剑指 Offer 51. 数组中的逆序对(C++) 归并排序

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

示例 1:

输入: [7,5,6,4]
输出: 5

限制:
0 <= 数组长度 <= 50000

解题思路:

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

基于归并排序的解法。

1、本题其实就是在对一个数组进行归并排序的基础上,增加了一个统计逆序对数目的障眼法,其实还是归并排序。
2、如果了解归并排序的话,就会想到我们可以用分治的思想,将给定的 nums 先一分为二,统计左半部分的逆序对数目,再统计右半部分的逆序对数目,以及统计跨越左半部分和右半部分的逆序对数目,然后将三者相加即可。

案例演示:假设 nums = [8,7,6,5,4,3,2,1]

**1、将 nums 一分为二。**则有 nums1 = [8,7,6,5],nums2 = [4,3,2,1]。那么 nums 的逆序对总数就等于 nums1 中的逆序对数目 + nums2 中的逆序对数目 + 跨越 nums1 和 nums2 的逆序对数目。接下来我们只演示 nums1 的后续步骤,因为 nums2 同理。
**2、将 nums1 再一分为二。**则有 nums3 = [8,7],nums4 = [6,5]。那么 nums1 的逆序对总数就等于 nums3 中的逆序对数目 + nums4 中的逆序对数目 + 跨越 nums3 和 nums4 的逆序对数目。接下来我们只演示 nums3 的后续步骤,因为 nums4 同理。
**3、将 nums3 再一分为二。**则有 nums5 = [8],nums6 = [7],而且 nums5 和 nums6 不能接着分了,因为它们分别只剩一个元素,不构成数对。这个时候,对于 nums5 和 nums6 来说,它们各自的逆序对数目都是 0。而对于 nums3 来说,它左半部分的逆序对数目等于 nums5 的逆序对数目(也就是 0);它右半部分的逆序对数目等于 nums6 的逆序对数目(也就是 0);它跨越左半部分和右半部分的逆序对数目是 1(因为 [8,7] 构成了一个逆序对),所以 nums3 的逆序对数目为 0 + 0 + 1 = 1。
4、而对于 nums1 来说,nums3 的逆序对数目等于 nums1 左半部分的逆序对数目,也就是我们刚刚求出来的 1。这时候我们还需要去求 nums1 右半部分的逆序对数目(也就是 nums4 的逆序对数目),再求跨越 nums3 和 nums4 的逆序对数目,然后再如法炮制算 nums2,然后才能得到最终 nums 的逆序对数目。
5、看到这里大家想必都能理解到,本题的核心思路其实就是分治的思想,也就是自顶向下地推理,自底向上地计算。而归并排序就很好地应用了分治思想,而且本题求逆序对的方法刚好可以借助归并排序,所以就采取这个方法。

作者:superkakayong
链接:https://leetcode-cn.com/problems/shu-zu-zhong-de-ni-xu-dui-lcof/solution/zi-jie-ti-ku-jian-51-kun-nan-shu-zu-zhon-eipc/
来源:力扣(LeetCode)
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

代码为官方答案

class Solution {
public:
    int mergeSort(vector<int>& nums, vector<int>& tmp, int l, int r) {
        if (l >= r) {//数组长度为1时, 不存在数对,直接 return 0
            return 0;
        }

        int mid = (l + r) / 2;//取中线
        int inv_count = mergeSort(nums, tmp, l, mid) + mergeSort(nums, tmp, mid + 1, r);// 计算左半部分的逆序对+计算右半部分的逆序对
        int i = l, j = mid + 1, pos = l;//指针 lPtr = 0 指向 L 的首部;rPtr = 0 指向 R 的头部,pos表示新数组的当前元素的位置,初始化为R
        while (i <= mid && j <= r) {
            if (nums[i] <= nums[j]) {
                tmp[pos] = nums[i];
                ++i;
                inv_count += (j - (mid + 1));//mid + 1到j-壹 的数已经放进去tmp数组,说明这些数比nums[i]小,
                //j-壹 减 mid + 1  +1 ==(j - (mid + 1))
				//因此,计算mid + 1到j-1的数
            }
            else {
                tmp[pos] = nums[j];//直接放进新的数组
                ++j;
            }
            ++pos;
        }
		
		//当右部分数组结束,但左部分数组还没有遍历结束;此刻j==r+壹
		//说明剩下来的这些数比右部分数组最大值都大
        for (int k = i; k <= mid; ++k) {
            tmp[pos++] = nums[k];
            inv_count += (j - (mid + 1));
        }
		//当左部分数组结束,但右部分数组还没有遍历结束
		//直接放进新的数组
        for (int k = j; k <= r; ++k) {
            tmp[pos++] = nums[k];
        }
        copy(tmp.begin() + l, tmp.begin() + r + 1, nums.begin() + l);//
//将数组tmp中的tmp.begin() + l为起点到 tmp.begin() + r + 1前一个元素    [tmp.begin() + l, tmp.begin() + r + 1)左闭右开区间
//的(r + 壹 -l )个元素复制到 nums容器中begin() + l的起始位置


        return inv_count;
    }

    int reversePairs(vector<int>& nums) {
        int n = nums.size();//数组长度
        vector<int> tmp(n);//新建一个n长度的数组
        return mergeSort(nums, tmp, 0, n - 1);//调用函数
    }
};

复杂度分析

记序列长度为 n。

时间复杂度:同归并排序 O(n log n)。
空间复杂度:同归并排序 O(n),因为归并排序需要用到一个临时数组。

作者:LeetCode-Solution
链接:https://leetcode-cn.com/problems/shu-zu-zhong-de-ni-xu-dui-lcof/solution/shu-zu-zhong-de-ni-xu-dui-by-leetcode-solution/
来源:力扣(LeetCode)
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值