在数组中的两个数字,如果前面一个数字大于后面的数字,则这两个数字组成一个逆序对。输入一个数组,求出这个数组中的逆序对的总数。
示例 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)
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。