逆序对及其变种问题的一点思考

来源:互联网 发布:张译 知乎回答的问题 编辑:程序博客网 时间: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。

如果本文的内容有任何不当之处,欢迎大家指正。

原创粉丝点击