计算逆序数对&&重要逆序数对(significant inversion)

逆序数对简介

  • 数组中的两个元素A[i], A[j],如果下标 i < j,但 A[i] > A[j] ,称 A[i] 与 A[j] 为逆序数对

计算逆序数对思路

  • Divide and Conquer
  • 将数组根据下标不断划分为两部分,然后从底至上计算每两部分间的逆序数对个数
    • 计算时,如果这两部分是 unstructured,那么就需要两层for循环,一一对比,O(n^2)
    • 引入一些结构,比如两部分都按照升序排好序,那么归并排序的combine时就可以直接得到计数,O(nlogn)
  • 完全利用归并排序,在 merge 时加入一句话即可:参见这篇文章

significant inversion 简介

  • 数组中的两个元素A[i], A[j],如果下标 i < j,但 A[i] > n*A[j] ,称 A[i] 与 A[j] 为逆序数对

significant inversion 思路

  • 思路与“普通逆序对”(i<j⋀ai>aj)的思路完全一致,也是在归并排序的归并过程中计算逆序对个数
  • 重要逆序对的判定条件(“前面的数是后面的两倍”)比归并条件(“前面的数比后面的数大”)更严格,因此要多加一层判断
  • 第一种是 在归并前另写一个循环单独计算重要逆序对,第二种是一 边排序一边计算

significant inversion 代码

#include<iostream>
#include<vector>
using namespace std;
int cnt = 0;
void SignificantInversion(vector<int> v,int l, int mid, int r){
    int i = l, j = mid+1;
    vector<int> tmp(v);
   // while(i <= mid && j <= r){
      //   if(tmp[i] > 2*tmp[j]){
        //    cnt += mid - i + 1;//左右两边是排好序的数组,如果当前左边的元素是右边元素
            //的二倍,那么左边数组后面的元素都比当前元素大二倍,这段长度就是中间减i
            //说实话,我看网上的公式都特别复杂,有没有人看一下这个对吗? =》 在leetcode 上验证是不对的,因为while 的判断条件,是限制为两个中一个遍历结束就会结束,而实际上剩下的另一个数组,仍然可能会有可行解
          //  j++;
         //}else{
          //  i++;
         //}
    //}
// 这里需要这种双层循环,让每个元素都有比较
     for (; i <= mid; ++i) {
      while (j <= r && (long)nums[i] > (long)nums[j] * 2) {
        j++;
      }
      cnt += j - (mid + 1);
    }
    i = l, j = mid+1;
    int k = l;
    while(i <= mid && j <= r){
        if(tmp[i] > tmp[j]){
            v[k++] = tmp[j++];
        }else
            v[k++] = tmp[i++];
    }
    while(i <= mid){
        v[k++] = tmp[i++];
    }
    while(j <= r)
        v[k++] = tmp[j++];
}
void CountInversion(vector<int> v, int l, int r){
    if(l < r){
        int mid = l + (r-l)/2;
        CountInversion(v,l,mid);
        CountInversion(v,mid+1,r);
        SignificantInversion(v,l,mid,r);
    }
}
int main(){
    vector<int> v{10,2};
    CountInversion(v,0,v.size()-1);//第二个参数是 n-1!!!
    cout<<cnt<<endl;

}
  • 吐槽一下,一道题做了n多个小时,本来可以自己想,硬是各种百度,看到各种晦涩难懂的答案,简直醉了。最主要的原因就是,自己只看了别人的普通逆序对是怎么做的,自己没去想,哭… 其实现在这个解法我也只是用几个案例跑了一下,也不知道对不对(卒)

  • 参考了这篇,还讲得比较清楚

  • 听别人说不能在排序的时候比较,但我这样做感觉也是对的呀!!

#include<iostream>
#include<vector>
using namespace std;
int cnt = 0;
void SignificantInversion(vector<int> v,int l, int mid, int r){
    int i = l, j = mid+1;
    int k = l;
    vector<int> tmp(v);

    while(i <= mid && j <= r){
        if(tmp[i] > tmp[j]){
            if(tmp[i] > 2*tmp[j]){
                cnt+=mid-i+1;
                //cout<<"i:"<<i<<"cnt:"<<cnt<<endl;
                }
            v[k++] = tmp[j++];
        }else{
            v[k++] = tmp[i++];
        }
    }
    while(i <= mid){
        v[k++] = tmp[i++];
    }
    while(j <= r){
        v[k++] = tmp[j++];
    }
}
void CountInversion(vector<int> v, int l, int r){
    if(l < r){
        int mid = l + (r-l)/2;
        CountInversion(v,l,mid);
        CountInversion(v,mid+1,r);
        SignificantInversion(v,l,mid,r);
    }
}
int main(){
    vector<int> v{5,9,3,5,3,1};
    CountInversion(v,0,v.size()-1);
    cout<<cnt<<endl;
}
  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值