逆序對介紹
如果存在正整數 i, j 使得 1 ≤ i < j ≤ n 而且 A[i] > A[j],則<A[i], A[j]> 這個有序對稱爲 A 的一個逆序對,也稱作逆序數。
求解一個數組中逆序對個數
其實就是求下標小的大數,那麼二話不說先來暴力,暴力顯然是O(n2),8太行
int unseq(vector<int> &nums)
{
int cnt = 0;
for(int i=0; i<nums.size(); i++)
for(int j=i+1; j<nums.size(); j++)
if(nums[i]>nums[j]) cnt++;
return cnt;
}
分治思想:藉助歸併排序
對於數組中的逆序對個數,將數組分爲[l, mid]
與[mid+1, r]
區間,問題可以分解爲子問題:
- 子數組
[l, mid]
的逆序對個數 lcnt - 子數組
[mid+1, r]
的逆序對個數 rcnt - A[i]位於左邊,A[j]位於右邊 且 A[i] > A[j] 的 <i, j> 組成的對
問題 1,2可以遞歸地求解,那麼關鍵是如何求問題3,即橫跨左右的對
注意這裏求解問題12的時候,不單單只是求解,還對數組排序了,也就是左邊/右邊都是有序的狀態,那麼我們可以用O(n)的時間,完成求解
- 在右側子數組中,從後往前枚舉 j 下標
- 在左側子數組中,設置指針 i ,一開始 i 指向mid
- i 向左滑動,找到第一個 i 使得 A[i]<=A[j]
- 說明前面
i+1 ~ mid
下標,A[i] 都大於 A[j] - 那麼對於 A[j] 爲右邊的逆序對,找到了
mid-i
對
因爲兩邊都有序,那麼對於 j 下標,因爲A[j]是遞減的,那麼A[j+1]配對的個數,同樣可以運用到A[j]
上,此外還要額外判斷A[j]是否能滿足更多逆序對
這裏利用兩邊數組的有序性,變相利用前面的結果,達到節省時間
看似兩重循環,其實 i,j 回退不超過 n/2 次,所以複雜度O(n)
用O(n)時間找打橫跨的逆序對個數,我們還要將數組歸併,方便上一層遞歸繼續查找
這裏使用【inplace_merge函數】進行歸併操作
這裏你可能會有很多問號:
排序之後對不是順序被打亂了嗎?
打亂順序(排序)之前,我們已經求得了兩邊子數組的解的個數
而且求解這一趟的橫跨的逆序對的時候,左右都排序了,但是他們相對位置不變,即左元素一定在左邊子數組,右邊同理
int unseq(vector<int> &nums, int l, int r)
{
if(l>=r || l<0 || r>=nums.size()) return 0;
int mid=(l+r)/2, cnt=0, i=mid;
int lcnt = unseq(nums, l, mid);
int rcnt = unseq(nums, mid+1, r);
for(int j=r; j>mid; j--)
{
while(i>=l && nums[j]<nums[i]) i--;
cnt += mid-i;
}
inplace_merge(nums.begin()+l, nums.begin()+mid+1, nums.begin()+r+1);
return lcnt+rcnt+cnt;
}
完整代碼
#include <bits/stdc++.h>
using namespace std;
int unseq(vector<int> &nums, int l, int r)
{
if(l>=r || l<0 || r>=nums.size()) return 0;
int mid=(l+r)/2, cnt=0, i=mid;
int lcnt = unseq(nums, l, mid);
int rcnt = unseq(nums, mid+1, r);
for(int j=r; j>mid; j--)
{
while(i>=l && nums[j]<nums[i]) i--;
cnt += mid-i;
}
inplace_merge(nums.begin()+l, nums.begin()+mid+1, nums.begin()+r+1);
return lcnt+rcnt+cnt;
}
int unseq(vector<int> &nums)
{
int cnt = 0;
for(int i=0; i<nums.size(); i++)
for(int j=i+1; j<nums.size(); j++)
if(nums[i]>nums[j]) cnt++;
return cnt;
}
int main()
{
vector<int> nums{2,7,4,3,1,8,6,5};
cout<<unseq(nums)<<endl;
cout<<unseq(nums, 0, nums.size()-1)<<endl;
for(int i=0; i<nums.size(); i++) cout<<nums[i]<<" ";
cout<<endl;
return 0;
}