当前位置: 首页 > news >正文

逆序对问题

2018-03-26 22:02:36

逆序对定义:

对于一个包含N个非负整数的数组A[1..n],如果有i < j,且A[ i ]>A[ j ],则称(A[ i] ,A[ j] )为数组A中的一个逆序对。
例如,数组(3,1,4,5,2)的逆序对有(3,1),(3,2),(4,2),(5,2),共4个。

逆序对问题就是求解一个数组中的逆序对的个数,逆序对问题是个非常经典的问题。当然可以写出一个在O(n ^ 2)时间复杂度解决的算法,这里就不对这种naive的解法做介绍了,主要会讲解两种O(nlogn)的解法。

 

一、树状数组

通过树状数组求解逆序对是个很经典的解法,树状数组的特点是单点更新,区间求和,使用树状数组求解逆序对从本质来说就是一个求和问题,我们可以为每个数字构建一个桶,出现了就在该桶里的数目上加1,可以倒序遍历数组,当遍历到某个数的时候,求getsum(nums[i]),即求解i位后的比a[i]小的数的个数,最后求总和就是结果。

桶排序的问题,这里也会出现,也就是数组中的区间范围如果非常庞大,那么建立max + 1大小的桶就非常的不合算,这时候就需要使用离散化的技术进行预处理。

使用树状数组的时间复杂度为O(nlogn)

  • 离散化
离散化,把无限空间中有限的个体映射到有限的空间中去,以此提高算法的时空效率。
通俗的说,离散化是在不改变数据相对大小的条件下,对数据进行相应的缩小。例如:
原数据:1,999,100000,15;处理后:1,3,4,2;
注意到,离散化后的数组中的逆序对和原数组是相等的。
在C++中可以借助STL进行离散化:
思路是:先排序,再删除重复元素,最后就是索引元素离散化后对应的值。
 
假定待离散化的序列为a[n],sub_a[n]是序列a[n]的一个副本,则对应以上三步为:
sort(sub_a,sub_a+n);
int size=unique(sub_a,sub_a+n)-sub_a;//size为离散化后元素个数
for(i=0;i<n;i++)
a[i]=lower_bound(sub_a,sub_a+size,a[i])-sub_a + 1;
  • 树状数组求解问题
    public List<Integer> countSmaller(int[] nums) {
        List<Integer> res = new ArrayList<>();
        int[] sorted = Arrays.copyOf(nums, nums.length);
        Arrays.sort(sorted);
        Map<Integer, Integer> ranks = new HashMap<>();
        int rank = 0;
        for (int i = 0; i < sorted.length; ++i)
            if (i == 0 || sorted[i] != sorted[i - 1])
                ranks.put(sorted[i], ++rank);
        int[] bit = new int[ranks.size() + 1];
        for (int i = nums.length - 1; i >= 0; --i) {
            int sum = query(bit, ranks.get(nums[i]) - 1);
            res.add(sum);
            update(bit, ranks.get(nums[i]), 1);
        }
        Collections.reverse(res);
        return res;
    }

    private int query(int[] bit, int i) {
        int res = 0;
        for (int k = i; k > 0; k -= (k & -k)) {
            res += bit[k];
        }
        return res;
    }

    private void update(int[] bit, int i, int delta) {
        for (int k = i; k < bit.length; k += (k & -k)) {
            bit[k] += delta;
        }
    }

 

二、归并排序

归并排序在merge的过程中天然会进行前后数的比较,因此使用归并排序来求解逆序对问题就水到渠成了。
时间复杂度: O(nlogn)
public class MergeSort {
    int merge(int[] nums, int[] tmp, int L, int R, int REnd) {
        int res = 0;
        int index = L;
        int len = REnd - L + 1;
        int LEnd = R - 1;
        while (L <= LEnd && R <= REnd) {
            if (nums[R] < nums[L]) {
                res += LEnd - L + 1;
                tmp[index++] = nums[R++];
            }
            else tmp[index++] = nums[L++];
        }
        while (L <= LEnd) tmp[index++] = nums[L++];
        while (R <= REnd) tmp[index++] = nums[R++];
        for (int i = 0; i < len; i++, REnd--) {
            nums[REnd] = tmp[REnd];
        }
        return res;
    }

    int mergeSort(int[] nums, int[] tmp, int L, int REnd) {
        if (L < REnd) {
            int mid = (REnd - L) / 2 + L;
            return mergeSort(nums, tmp, L, mid) + mergeSort(nums, tmp, mid + 1, REnd) +
                    merge(nums, tmp, L, mid + 1, REnd);
        }
        return 0;
    }

    public static void main(String[] args) {
        int[] nums = new int[]{2, 3, 8, 6, 1};
        MergeSort ms = new MergeSort();
        System.out.println(ms.mergeSort(nums, new int[nums.length], 0, nums.length - 1));
    }
}

 

 

 

转载于:https://www.cnblogs.com/TIMHY/p/8654316.html

相关文章:

  • 14.boost最小生成树 kruskal_min_spainning_tree
  • CAP原则(CAP定理)、BASE理论
  • Google I/O 2014 大会总结 Android开发新方向
  • 模板中可以直接使用函数设定数据集,而不需要在控制器中给模板变量赋值传入数据集变量,如:...
  • 预防定时重启apache服务没有起来的脚本
  • iframe的用法
  • Unix系统编程()brk,sbrk
  • linux audit审计(2)--audit启动
  • 完美洗牌算法
  • STL::sort函数实现
  • Android中Activity和Service的数据通讯
  • X-Forwarded-For 和 X-Real-IP 的区别?
  • python的列表生成式
  • Angular2.0的学习(三)
  • 程序员如何利用空闲时间挣零花钱
  • 分享的文章《人生如棋》
  • Android Volley源码解析
  • CentOS 7 防火墙操作
  • CentOS6 编译安装 redis-3.2.3
  • Debian下无root权限使用Python访问Oracle
  • java中的hashCode
  • vue-cli3搭建项目
  • Vue--数据传输
  • 阿里中间件开源组件:Sentinel 0.2.0正式发布
  • 基于 Ueditor 的现代化编辑器 Neditor 1.5.4 发布
  • 数据库写操作弃用“SELECT ... FOR UPDATE”解决方案
  • mysql面试题分组并合并列
  • 翻译 | The Principles of OOD 面向对象设计原则
  • # 透过事物看本质的能力怎么培养?
  • #{} 和 ${}区别
  • #【QT 5 调试软件后,发布相关:软件生成exe文件 + 文件打包】
  • $.extend({},旧的,新的);合并对象,后面的覆盖前面的
  • (01)ORB-SLAM2源码无死角解析-(56) 闭环线程→计算Sim3:理论推导(1)求解s,t
  • (C语言)深入理解指针2之野指针与传值与传址与assert断言
  • (java版)排序算法----【冒泡,选择,插入,希尔,快速排序,归并排序,基数排序】超详细~~
  • (poj1.3.2)1791(构造法模拟)
  • (Redis使用系列) SpringBoot 中对应2.0.x版本的Redis配置 一
  • (板子)A* astar算法,AcWing第k短路+八数码 带注释
  • (第61天)多租户架构(CDB/PDB)
  • (二) Windows 下 Sublime Text 3 安装离线插件 Anaconda
  • (二十五)admin-boot项目之集成消息队列Rabbitmq
  • (免费分享)基于springboot,vue疗养中心管理系统
  • (译) 理解 Elixir 中的宏 Macro, 第四部分:深入化
  • (最优化理论与方法)第二章最优化所需基础知识-第三节:重要凸集举例
  • *1 计算机基础和操作系统基础及几大协议
  • .h头文件 .lib动态链接库文件 .dll 动态链接库
  • .NET Framework 4.6.2改进了WPF和安全性
  • .NET HttpWebRequest、WebClient、HttpClient
  • .net 中viewstate的原理和使用
  • .NET/C# 利用 Walterlv.WeakEvents 高性能地定义和使用弱事件
  • @FeignClient注解,fallback和fallbackFactory
  • @Tag和@Operation标签失效问题。SpringDoc 2.2.0(OpenApi 3)和Spring Boot 3.1.1集成
  • [ 代码审计篇 ] 代码审计案例详解(一) SQL注入代码审计案例
  • [.NET 即时通信SignalR] 认识SignalR (一)
  • [16/N]论得趣