逆序对及其变种问题的一点思考
来源:互联网 发布:张译 知乎回答的问题 编辑:程序博客网 时间:2024/05/16 10:07
逆序对问题:
Given an array nums, we call (i, j) an important reverse pair if i < j and nums[i] > nums[j].
You need to return the number of important reverse pairs in the given array.
经典的解法是用归并排序(Merge Sort)顺带解决。当归并排序的时候出现右半数组非空,且右边对应元素小于左边对应元素时, 则左半数组所有剩余元素都比右半数组当前元素大,所以在相应位置加上
num_of_reverse_pair+=m-p1;
即可。
该题需要注意的是对于数组中存在相同元素的处理。如果数组中存在相同元素, 则
reverse_pair()函数中
else if (a[p1]<=a[p2]) { t[p++] = a[p1++]; }
必须用<=,而不能是<, 即使在后面的else内加上if (a[p1]>a[p2])以区分(a[p1]=a[p2])也不行。原因如下:
假设左半数组还剩[5,8,9],右半数组也剩[5,8,9],如果上面是<,
则程序将右半数组的5拷贝到临时数组t中,右半数组还剩[8,9]。然后程序看到左半数组的5小于右半数组的8, 又将左半数组的5拷贝到t中,现在左右数组都是[8,9],又重复相同步骤,直到所有元素拷贝到t中,这里num_of_reverse_pair并没有增加,结果少了3个pair: [8, 5], [9, 5], [9, 8]。
如果采用<=,则程序是将左半数组的5拷贝到t中,然后程序发现左半数组的8大于右半数组的5,num_of_reverse_pair会增加2,然后程序将右半数组的5拷到t中,现在左右数组都是[8,9]。同样,程序也会先拷左半数组的8,然后num_of_reverse_pai又会增加1, 因为9>8。
#include <iostream>using namespace std;int num_of_reverse_pair=0;void merge_sort(int *a, int x, int y, int *t) { if (y == x+1) return; int m = x + (y-x)/2; merge_sort(a, x, m, t); merge_sort(a, m, y, t); int p1=x, p2=m; //pointers to the left half array and right half array, respectively. int p=x; //pointer to the array t while(p1<m || p2<y) { // if right half array empty, copy the rest of left half array to t if (p2 >= y) { t[p++] = a[p1++]; } else if (p1 >= m) { t[p++] = a[p2++]; } else if (a[p1]<a[p2]) { t[p++] = a[p1++]; } else { t[p++] = a[p2++]; } } for (int i=x; i<y; i++){ a[i] = t[i]; }}void reverse_pair(int *a, int x, int y, int *t) { if (y == x+1) return; int m = x + (y-x)/2; reverse_pair(a, x, m, t); reverse_pair(a, m, y, t); int p1=x, p2=m; //pointers to the left half array and right half array, respectively. int p=x; //pointer to the left half array while(p1<m || p2<y) { //right side is empty if (p2 >= y) { t[p++] = a[p1++]; } else if (p1 >= m) { //left side is empty t[p++] = a[p2++]; } //注意, 对于逆序对问题,这里必须用<= !!! else if (a[p1]<=a[p2]) { //both sides are non-empty, and left item is smaller than right item t[p++] = a[p1++]; } else { num_of_reverse_pair+=m-p1; for (int i=p1; i<m; i++) { cout<<"("<<a[i]<<", "<<a[p2]<<")"<<endl; } t[p++] = a[p2++]; } } for (int i=x; i<y; i++){ a[i] = t[i]; } return;}int main(){ int arr[14] = {-1, 7, 9, 23, 5, 8, 94, 128, 8, 8, 7, 9, 10, 23}; cout<<"original array:"<<endl; for (int i=0; i<sizeof(arr)/sizeof(int); i++) cout<<arr[i]<<" "; cout<<endl; int t[sizeof(arr)/sizeof(int)]; reverse_pair(arr, 0, sizeof(arr)/sizeof(int), t); cout <<"num of reverse pairs are " << num_of_reverse_pair <<endl; cout <<"sorted array is"<<endl; for (int i=0; i<sizeof(arr)/sizeof(int); i++) cout<<arr[i]<<" "; cout<<endl; return 0;}
逆序对问题还有一个变种, 也就是LeetCode 493。
Given an array nums, we call (i, j) an important reverse pair if i < j and nums[i] > 2*nums[j]. 注意这里的2。
对于这个变种问题,我们不能仅仅把上面的else{}加上if (a[p1] > 2*a[p2])如下:
else{ if (a[p1] > 2*a[p2]) { num_of_reverse_pair+=m-p1; for (int i=p1; i<m; i++) { cout<<"("<<a[i]<<", "<<a[p2]<<")"<<endl; } } t[p++] = a[p2++]; }
这样会导致一些情况漏判,比如说如果a[]={2,4,3,5,1},归并排序的过程中a[]会变成{2,4,1,3,5}。
当p1=0, p2=2时,因为a[0]=2不比a[2]=1的两倍大,所以上述if条件不满足,p2++,这样1这个数就漏网了,p1再++时,后面的4便没有机会与1比较。
我采用的方法是在上面的else内,直接从[p1,m)遍历,如果有元素比a[p2]大,则count++。代码如下:
else{ for (int i=p1; i<m; i++) { if (a[i] > 2L*a[p2]) { num_of_reverse_pair+=1; cout<<" ("<<a[i]<<", "<<a[p2]<<") "; } cout<<endl; } t[p++] = a[p2++];}
另外要注意2*a[p2]时可能导致溢出,所以要用2L*a[p2]将其转化为long。
注意,上面的算法的复杂度变成了O(n^2logn),其实还不如来两重循环直接查找。不过,我们还是可以优化该算法的。考虑到a[p1..m)已经排好序,我们可以用二分查找来找这期间比2*a[p2]大的数。这样,算法复杂度就变成了O(n(logn)^2)。注意打印pairs的那个循环不算。
完整代码如下:
int lower_bound_special(int *a, int x, int y, int v) { int m; while(x<y) { m = x + (y-x)/2; if (a[m] <= v) x=m+1; else y=m; } return x;}void reverse_pair_2(int *a, int x, int y, int *t) { if (y == x+1) return; int m = x + (y-x)/2; reverse_pair_2(a, x, m, t); reverse_pair_2(a, m, y, t); int p1=x, p2=m; //pointers to the left half array and right half array, respectively. int p=x; //pointer to the left half array while(p1<m || p2<y) { //right side is empty if (p2 >= y) { t[p++] = a[p1++]; } else if (p1 >= m) { //left side is empty t[p++] = a[p2++]; } else if (a[p1]<=a[p2]) { //both sides are non-empty, and left item is smaller than or equal to the right item t[p++] = a[p1++]; } else { int find_index = lower_bound_special(a, p1, m, 2L*a[p2]); if (find_index > 0) { num_of_reverse_pair += m-find_index; for (int i=find_index; i<m; i++) { cout<<" ("<<a[i]<<", "<<a[p2]<<") "; } cout<<endl; } t[p++] = a[p2++]; } } for (int i=x; i<y; i++){ a[i] = t[i]; } return;}
那个lower_bound_special()函数解释一下,它返回a数组[x,y)的范围内,比v大的第一个index。也就是如果a[2] <= v < a[3],则返回3。如果a[x..y)都小于或等于v,则返回y。所以,它的输入范围是[x,y),输出范围是[x,y]。我们仔细分析一下:
a[m] == v时,我们应该去[m+1,y)找,所以x=m+1;
a[m] > v时,m是一个可选项,但前面可能还有,所以应该去[x,m)找,所以y=m;
a[m] < v时,应该从m+1开始找,所以x=m+1.
把case 1和case 3合并,便是a[m]<=v时,x=m+1。
如果本文的内容有任何不当之处,欢迎大家指正。
- 逆序对及其变种问题的一点思考
- 汉诺塔问题及其变种
- 对“最大子序列和问题”的一点思考
- 对计算机技术的一点思考
- 对中断的一点思考
- 对FriendFeed的一点思考
- 对测试的一点思考
- 对项目的一点思考
- 对学术研究的一点思考
- 对项目的一点思考
- 对人生的一点思考
- 对编程的一点思考
- 对LCS算法及其变种的初步研究
- 思考问题的本质--对软件设计中抽象层意义的一点思考
- Josephus约瑟夫问题及其变种
- 对学术规范的一点思考
- 对临时对象的一点思考
- 对易语言的一点思考。
- POJ 2253 Frogger Dijkstra变形
- Servlet细节———< load-on-startup >配置
- spring-boot实战:shiro
- luogu1155【2008提高】双栈排序(二分图判断+模拟)
- 机器学习 学习笔记
- 逆序对及其变种问题的一点思考
- Ubuntu 16.04 安装caffe(CPU)以及编译问题处理
- Cheapest Palindrome POJ
- SSM整合(springmvc + spring + mybatis)
- POJ
- vim的玩法
- Codeforces Round #437 (Div. 2, based on MemSQL Start[c]UP 3.0
- gcc编译器的简介与使用
- Unity学习笔记6-进程、线程和协程