序文
前に一度書かれました。。。
https://blog.csdn.net/weixin_44176696/article/details/105092613
書き方が悪いので、今日は簡単なリメイクに行きます。。。
タイトル説明
配列内の2つの数値の場合、最初の数値が次の数値より大きい場合、2つの数値は逆のペアを形成します。配列を入力して、この配列のリバースペアの総数を見つけます。
例1:
输入: [7,5,6,4]
输出: 5
制限:
0 <= 数组长度 <= 50000
アイデア
配列内の逆順序対を見つけます。つまり、2つの添え字(i、j)を見つけます。ここで、i、jの分布は次の3つのケースに分けられます。
- i、jは配列の左半分にあります
- i、jは配列の右半分にあります
- iは配列の左半分にあり、jは配列の右半分にあります
分割統治法を使用してケース1と2を見つけるのは非常に簡単で、再帰によって実現できます。難しさは主にケース3の解決策、つまりiとjが配列の両方の半分にある場合にあります。強引な列挙が使用されている場合、分割統治の合併コストは依然としてO(n^2)
ただし、マージソートの特性を使用できます。つまり、分割統治後のマージソートを使用すると、配列の左右が順番に増加します。iとjが配列の2つの半分にある状況のみを考慮しているため、それらの順序は重要ではありません。
右側の点jを列挙します。各点jについて、左半分に最初のi添え字が見つかるのでnums[i]<=nums[j]
、これは、区間[i + 1、mid]の点がすべて満たされていることを意味しますnums[i]>nums[j]
。したがって、右側の場合エンドポイントjには、次のmid-i
反転があります。
図に示すように、黄色のボックス内の点xは nums[x]>nums[j]
さらに、左半分と右半分が単調に増加しているため、列挙jの順序は右から左にする必要があります。これは次の理由によるものです。
- jをn回列挙すると、このとき
右端点=j
、nums[i]>nums[j]
- n + 1回の列挙j、現時点では、まだ確立されている
右端点=j-1
ためnums[j-1]<nums[j]
nums[i]>nums[j-1]
つまり、前回の結果を再利用して、左端点iの繰り返しの動きを減らします。左の点imidから列挙を開始してから、l-1
移動回数を超えない位置に移動するmid-l
ため、合併の費用は依然としてO(n)
コード
class Solution {
public:
vector<int> nums;
int divide(int l, int r)
{
// 边界 -- 长度为1的区间没有逆序对
if(l<0 || r>=nums.size() || l==r) return 0;
int ans=0, mid=(l+r)/2;
ans += divide(l, mid); // 逆序对两点都在左半边
ans += divide(mid+1, r); // 逆序对两点都在右半边
// 逆序对两点一个在左半边一个在右半边
int i = mid;
for(int j=r; j>=mid+1; j--)
{
while(i>=l)
{
if(nums[i]<=nums[j]) break; // 找第一个 nums[i]<=nums[j]
i--;
}
ans += mid-i; // [i+1, mid] 区间的数都大于 nums[j]
}
// 归并排序
auto b = nums.begin();
inplace_merge(b+l, b+mid+1, b+r+1);
return ans;
}
int reversePairs(vector<int>& nums) {
if(nums.size()==0) return 0;
this->nums = nums;
return divide(0, nums.size()-1);
}
};