逆序对的两种解法(归并排序和树状数组)及其变种问题的一点思考

逆序对问题:
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.
比如说数组arr[]={3,5,4,2,1},则应该输出8。因为(3,2)(3,1)(5,4)(5,2)(5,1)(4,2)(4,1)(2,1)都是逆序。

解法1: 用归并排序(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。

解法2:采用树状数组。这里的数组a[]={3,5,4,2,1}里的数对应树状数组里面的下标。

这里的代码参考了
http://www.cnblogs.com/xiongmao-cpp/p/5043340.html

#include <iostream>
#include <cstring>

using namespace std;
#define N 1010
int c[N];
int n;
int lowbit(int i)
{
    return i&(-i);
}
int insert(int i)
{
    while(i<=n){
        c[i]+=1;
        i+=lowbit(i);
    }
    return 0;
}

int getsum(int i)
{
    int sum=0;
    while(i>0){
        sum+=c[i];
        i-=lowbit(i);
    }
    return sum;
}
void output()
{
    for(int i=1;i<=n;i++) cout<<c[i]<<" ";
    cout<<endl;
}

int main()
{
    while(cin>>n){
        int ans=0;
        memset(c,0,sizeof(c));
        for(int i=1;i<=n;i++){
            int a;
            cin>>a;
            insert(a);

            cout<<"input "<<a<<endl;
            output();

            ans+=i-getsum(a); //统计当前序列中大于a的元素的个数,即对应a的逆序
            cout<<"ans= "<<ans<<endl;

        }
        cout<<ans<<endl;
    }
    return 0;
}

注意:
1) lowbit(i)返回的是i的二进制表示中i的最后一个1及其后面的0所表示的值。比如说i=5,二进制0101,lowbit(5)=1。i=6,二进制0110,lowbit(6)=2。i=8,二进制1000,lowbit(8)=8。我们看下图就可以发现lowbit(i)就表示C[i]所管辖的A[i]的个数。注意上面的程序里面没有A[i],其输入值代表A[i]的下标。比如说i=7,则A[7]=1。没有输入的i的对应的A[i]=0。

这里写图片描述

2) insert(i)表示在加入A[i] (也即A[i]=1)的时候,对应的C[i]的值都要+1。比如说a=3,则c[3],c[4]和c[8]都要+1。那我们怎么从3找到4,又从4找到8呢?这就要用到lowbit()。3的lowbit是1,3+1=4。4的lowbit是4, 4+4=8。
要注意的是input(i)里面的循环仅限于i<=n,如果我们的输入数组元素个数只有5,则a=3的时候找到4就可以了,因为再往上面找就是8,已经越界了。我们把c[3]和c[4]都+1就可。

3) getsum(a)表示从A[1]到A[i] (也就是到a为止的所有输入)共有多少个元素小于等于a。我们可以看出还是用lowbit()。比如说a=5,lowbit(5)=1,5-1=4,lowbit(4)=4,4-4=0。我们把c[4]和c[5]加起来即可。这里要加起来是因为5不是2的幂,所以c[4]和c[5]互不包含。而如果a=8, lowbit(8)=8, 直接返回c[8]即可。
注意这里A[i]如果存在就是1,否则为0。所以getsum(a)实际上就是A[1]..A[i]中小于等于a的元素个数总和。那么i-gesum(a)就是A[1]..A[i]中大于a的元素个数,也就是对应a的逆序对个数。我们把所有的输入a对应的逆序对个数加起来就是答案。

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

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值