• 逆序对问题


    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));
        }
    }
    
     
  • 相关阅读:
    关于MySQL中ALTER TABLE 的命令用法——SQL
    replace函数——SQL
    SQL构造一个触发器
    【视频转换】监控视频DAV转mp4
    【pyqt5+opencv】如何将大量图片合成一张图
    【OpenCV+pyqt5】视频抽帧裁剪与图片转视频
    【Caffe】生成数据之修改label
    【labelme】标注工具Trick
    【OpenCV+pyqt5】视频抽帧相关操作
    【pyqt5】Pyinstaller封装OpenCV异常
  • 原文地址:https://www.cnblogs.com/hyserendipity/p/8654316.html
Copyright © 2020-2023  润新知