面试题51:数组中的逆序对
目录
题目
在数组中的两个数字如果前面一个数字大于后面的数字,则这两个数字组成一个逆序对。输入一个数组,求出这个数组中的逆序对的总数。
思路
最直观的想法是两两比较(比较笨,比较老实的方法)一个一个来判断逆序对,性能的瓶颈主要在于比较的次数太多,所以我们需要比较聪明的方法,
- ↓如果数组稍微有序点,就不需要一个一个地判断逆序对,可以根据有序性,比较聪明的一下子得出好几个逆序对,需要比较的次数将大大减少。
- ↓这两个有序子列中所有元素的逆序关系确定之后,它们现在的相对位置已经没有利用价值了(对于这两个子序列中的所有元素与其他的元素的逆序关系判断已无影响),
- ↓可以对这两个子序列有序化,方面它们与其他元素逆序对的判断。
这个问题一个好理解的方法的是直接调用递归归并排序的过程,只不过会多一个从temp数组中把排好序的子列复制回data的操作过程,这个解法直观好理解,这里的解法使用了一个优化技巧:让temp初始化为和data一模一样,然后交替使用data和temp进行递归归并过程,不太好理解,但是只要把握住我们求的子序列范围内的逆序对准确无误即可,这里并不要求最后data一定是归并排好序的。
解法
C++实现1
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 | int InversePairs( int * data, int length) //主函数,外壳函数 { if (data == nullptr || length < 0) return 0; int * copy = new int [length]; //由于我们要交替使用data和copy,所以我们要确保data和copy对应的 //范围内元素一样,这样计算出来的逆序对才正确 for ( int i = 0; i < length; ++i) copy[i] = data[i]; int count = InversePairsCore(data, copy, 0, length - 1); delete [] copy; return count; } //这个函数的作用:借助于copy数组,计算出data中范围[start,end]中的逆序对总数 int InversePairsCore( int * data, int * copy, int start, int end) //核心函数 { if (start == end) { copy[start] = data[start]; //此递归函数退出时递归函数的参数copy的状态变为函数参数data排序后的结果 return 0; //函数参数data的状态不确定(因为递归调用的过程中data也会扮演辅助数组的角色) } int length = (end - start) / 2; //注意这个函数的形参为(data,copy),这里的调用实参为(copy,data) int left = InversePairsCore(copy, data, start, start + length); int right = InversePairsCore(copy, data, start + length + 1, end); //i始终指向前半部分有序序列的最大元素 int i = start + length; //j始终指向后半部分有序序列的最大元素 int j = end; int indexCopy = end; int count = 0; // 这里的两个有序子序列之间的逆序对计数是以遍历前半部分每一个元素与后 // 半部分子序列的逆序对为准进行的,如果前半部分的元素a刚好大于后半部分的某一 // 元素b(后半部分b之后的元素大于等于a),由于子数组是有序的,那么可以直接 // 算出a与后半部分所构成的逆序对数值,而要得出前半部分a之前元素aa与后半部分构成 // 的逆序对计数值,我们不需要从后半部分的最大元素end处开始试探,可以在处理a时 // 的基础上移动指针,因为如果a刚好>b,那么后半部分b之后元素>=a,当然更>=aa,所以 // 对于aa来说,后半部分b之后元素不用考虑,而aa可能>b,所以对于aa而言,可以在b位置 // 基础上移动指针,切记:我们这里的计数值是以前半部分元素为标准来计数,通过上面的描述过程 // 指针只能从大到小移动 while (i >= start && j >= start + length + 1) { if (data[i] > data[j]) { copy[indexCopy--] = data[i--]; count += j - start - length; } else { copy[indexCopy--] = data[j--]; } } for (; i >= start; --i) copy[indexCopy--] = data[i]; for (; j >= start + length + 1; --j) copy[indexCopy--] = data[j]; return left + right + count; } |
C++实现2
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 | #include <iostream> using namespace std; int inverse_pairs_core( int nums[], int temp[], int start, int end) { if (start >= end) return 0; int mid = start + (end - start) / 2; int left_cnt = inverse_pairs_core(nums, temp, start, mid); int right_cnt = inverse_pairs_core(nums, temp, mid + 1, end); int cnt = left_cnt + right_cnt; int i = start, j = mid + 1, k = start; while (i <= mid && j <= end) { //逆序对的计数,以后半段j为中心进行统计 //使得以j为结尾的逆序对的统计做到不重不漏,论证过程可以用反正法进行证明 if (nums[i] > nums[j]) { cnt = cnt + (mid - i + 1); temp[k++] = nums[j++]; } else { temp[k++] = nums[i++]; } } while (i <= mid) { temp[k++] = nums[i++]; } while (j <= end) { temp[k++] = nums[j++]; } for ( int index = start; index <= end; index++) { nums[index] = temp[index]; } return cnt; } int inverse_pairs( int nums[], int n) { int *temp = new int [n]; int cnt = inverse_pairs_core(nums, temp, 0, n - 1); delete [] temp; return cnt; } int main() { int nums[] = {4, 2, 2, 8, 5, 2, 7, 3}; int cnt = inverse_pairs(nums, 8); cout << "the inverse pairs cnt is :" << cnt << endl; return 0; } |
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】凌霞软件回馈社区,博客园 & 1Panel & Halo 联合会员上线
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】博客园社区专享云产品让利特惠,阿里云新客6.5折上折
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 微软正式发布.NET 10 Preview 1:开启下一代开发框架新篇章
· 没有源码,如何修改代码逻辑?
· PowerShell开发游戏 · 打蜜蜂
· 在鹅厂做java开发是什么体验
· WPF到Web的无缝过渡:英雄联盟客户端的OpenSilver迁移实战
2021-03-02 excel中常规格式和日期格式的转换规则