LeetCode/数组中的逆序对

在数组中的两个数字,如果前面一个数字大于后面的数字,则这两个数字组成一个逆序对。输入一个数组,求出这个数组中的逆序对的总数。

1. 归并排序

通过归并递归,先拆分成子问题,先计算区间内的逆序数,然后归并,同时计算区间之间的逆序数,并把总数加起来
计算两区间之间的逆序对时,可以跟合并区间操作统一起来,在归并合并区间时,我们使用了两个指针记录两区间初始位置
相互比较大小,复制给辅助数组,并轮流前进,以从前往后的方向为例,当右指针指向数小于左指针,那么它也必然小于左指针之后的数
直接计算得到了这个数相对左区间的逆序对,这是就利用了归并区间内有序的性质和区间相对位置确定的性质

class Solution {
public:
    int reversePairs(vector<int>& nums) {
        vector<int> tmp(nums.size());
        return mergeSort(0, nums.size() - 1, nums, tmp);//归并排序
    }
private:
    int mergeSort(int l, int r, vector<int>& nums, vector<int>& tmp) {
        // 终止条件
        if (l >= r) return 0;//逆序数返回0
        // 递归划分
        int m = (l + r) / 2;
        int res = mergeSort(l, m, nums, tmp) + mergeSort(m + 1, r, nums, tmp);//计算各自区间内的逆序对并排序
        // 合并阶段,计算两区间之间的逆序对
        int i = l, j = m + 1;//记录左右区间首指针
        for (int k = l; k <= r; k++)//将已排序两区间元素复制过来
            tmp[k] = nums[k];
        for (int k = l; k <= r; k++) {//排序合并
            if (i == m + 1)//左指针超出范围
                nums[k] = tmp[j++];//将右区间剩余元素补充上去
            else if (j == r + 1 || tmp[i] <= tmp[j])//右指针超出范围或者左侧元素更小
                nums[k] = tmp[i++];//将左区间元素补充上去
            else {//右侧元素更小,且都没超出范围,此时i~m元素都比 j 大
                nums[k] = tmp[j++];//插入右区间元素
                res += m - i + 1; // 统计逆序对
            }
        }
        return res;
    }
};

2. 离散化树状数组

class Solution {
public:
    int reversePairs(vector<int>& nums) {
        n = nums.size();
        vector<int> tmp = nums;
        // 离散化
        sort(tmp.begin(), tmp.end());//从小到大排序
        for (int& num: nums)  //nums记录每个值的坐标,将值离散化,只用记录相对位置信息
            num = lower_bound(tmp.begin(), tmp.end(), num) - tmp.begin() + 1;//坐标从1开始,相同值只记第一个,因为相对位置对其他值没影响,同时防止把相同值计入前缀和
        // 树状数组统计逆序对
        tree.resize(n+1);//根据相对位置建树
        int ans = 0;
        for (int i = n - 1; i >= 0; --i) {//从后往前更新并判断是否逆序
            ans += getsum(nums[i]-1);
            update(nums[i],1);//单点更新,也就是插一个旗子,已有的旗子必然是排在后面的数,所以可以求前缀和判断逆序
        }
        return ans;
    }
    int n;
    vector<int> tree;
    int lowbit(int x){//求二进制化最后一位的值
        return x&(-x);
    }
    void update(int i,int k){ //在i位置加上k,O(logn)复杂度单点修改
        while(i<=n){//更新子树上所有值
            tree[i]+=k;
            i+=lowbit(i);//移动到父亲节点
        }
    }

    long long getsum(int i){  //求数组前i项的和
        long long res=0;
        while(i>0){//O(logn)求前缀和
            res+=tree[i];
            i-=lowbit(i);//移动到前一棵子树(子区间)
        }
        return res;
    }
};
posted @ 2022-06-23 17:13  失控D大白兔  阅读(138)  评论(0编辑  收藏  举报