每日一題,防止癡呆 = =
一、題目大意
在數組中的兩個數字,如果前面一個數字大於後面的數字,則這兩個數字組成一個逆序對。輸入一個數組,求出這個數組中的逆序對的總數。
二、題目思路以及AC代碼
這個題目就很明確,就是要求一個數組裏的逆序對,按道理是有套路方法的,不過很可惜,我之前並沒有看過 = =,所以就學習了一下基於歸併排序思路的解法和樹狀數組的解法。
首先,基於歸併排序思路的解法是這樣的。考慮在一般的歸併排序算法,首先將要排序的數組分爲左右兩部分,然後遞歸分別對左右兩部分的子數組進行排序,然後再將兩個有序的子數組進行合併。那麼在合併的時候,我們就可以看出這個數組中的逆序對個數。
舉個例子。
Left: [1, 4, 5, 7, 8]
Right: [2, 3, 6, 9, 10]
假設以上是在合併過程中需要進行合併的兩個子數組,那麼合併過程應該是這樣的
- 兩個指針lptr和rptr分別指向左數組和右數組的第一個元素,即1和2,比較,發現1比較小,然後把1放入合併之後的數組,lptr++
- 然後繼續比較4和2,此時2比較小,那麼把2放入合併之後的數組,rptr++
- 繼續比較4和3,此時3比較小,把3放入合併之後的數組,rptr++
- 繼續比較4和6,此時4比較小,把4放入合併之後的數組,但此時發現逆序對了沒有?不要忘了,Left數組中的元素本來都是在Right數組的右邊的,但此時經過排序卻有兩個右邊的數(2,3)被排到了左邊,說明在原數組中(4,2),(4,3)就是兩個逆序對。
- …
用這樣的思路,我們只需要對歸併排序的merge過程做一些操作,就可以把數組中的逆序對個數找出來啦!
然後,在來一個樹狀數組的思路,其實我感覺一般情況下是會先想到這個思路的。我們要找逆序對,無非是想找給定了一個位置,出現在這個位置之後,比這個位置的數小的數有多少個(這句話比較繞,好好捋捋,語文水平有限= =),那麼我們可以這樣,建立一個計次數組用來輔助,還是舉個例子說明比較好。
[5, 5, 2, 4, 6]
那麼我們可以建立一個計次數組cnt,[0, 0, 1, 0, 1, 2, 1],其中cnt[i]表示 i 在原數組中出現的次數,那麼我再對原數組從後向前遍歷,遍歷到nums[i]的時候,cnt[nums[i]]++,並且將結果加上在cnt數組中nums[i]-1的前綴和即可。因爲我們從後向前遍歷,所以保證了當我們遍歷到nums[i]的時候,已經在cnt中計數的都是在原數組中出現在nums[i]之後的數,所以如果在cnt數組中nums[i]-1的前綴和有值,那就說明在原數組中,nums[i]之後存在比nums[i]小的值,也就是逆序對。
但是這樣會發現幾個問題:
- 計次數組的大小是根據原數組中數的取值範圍決定的,可能會很大
- 如果單純的用cnt數組的話,是一個O(n^2)時間複雜度的結果
針對第一個問題,我們因爲只需要考慮元素的大小關係,所以可以排序之後,用他們的排名替換他們的值,那樣原數組的值域就限制在[0, size-1]了,當然爲了之後樹狀數組的使用,這裏把其限制在[1, size]。
針對第二個問題,最好的方法就是採用樹狀數組了,因爲樹狀數組建立好後,查找一次前綴和的複雜度是O(logn),所以總的時間複雜度是O(nlogn)。
如果對樹狀數組不是很熟悉的,建議看我轉載的另一篇博客,講的很清楚。
https://blog.csdn.net/m0_38055352/article/details/105726733
下面給出AC代碼:
歸併排序:
int merge_sort(vector<int>& a, vector<int>& tmp, int l, int r) {
int a_size = a.size();
if (l >= r) return 0;
int mid = (l + r) / 2;
int inv_count = merge_sort(a, tmp, l, mid) + merge_sort(a, tmp, mid+1, r);
int i = l, j = mid+1;
// merge
int pos = l;
while (i <= mid && j <= r) {
if (a[i] <= a[j]) {
tmp[pos++] = a[i];
i++;
inv_count += j - (mid + 1);
}
else {
tmp[pos++] = a[j];
j++;
}
}
for (;i<=mid;i++) {
tmp[pos++] = a[i];
inv_count += (j - (mid + 1));
}
for (;j<=r;j++) {
tmp[pos++] = a[j];
}
copy(tmp.begin() + l, tmp.begin() + r + 1, a.begin() + l);
return inv_count;
}
class Solution {
public:
int reversePairs(vector<int>& nums) {
int n_size = nums.size();
vector<int> tmp(n_size);
int inv_count = merge_sort(nums, tmp, 0, n_size-1);
return inv_count;
}
};
樹狀數組
class BIT {
private:
vector<int> _tree;
int _n;
public:
BIT(int n) : _n(n), _tree(n+1) {}
int lowbit(int m) {
return m & (-m);
}
int get_sum(int x) {
int res = 0;
while (x) {
res += _tree[x];
x -= lowbit(x);
}
return res;
}
void update(int x, int add) {
while (x <= _n) {
_tree[x] += add;
x += lowbit(x);
}
}
};
class Solution {
public:
int reversePairs(vector<int>& nums) {
int n = nums.size();
// 離散化
vector<int> tmp = nums;
sort(tmp.begin(), tmp.end());
for (int& num: nums) {
// 這裏+1是因爲lowbit(0)是0
num = lower_bound(tmp.begin(), tmp.end(), num) - tmp.begin() + 1;
}
// 求逆序對
BIT tree(n);
int ans = 0;
for (int i=n-1;i>=0;i--) {
ans += tree.get_sum(nums[i] - 1);
tree.update(nums[i], 1);
}
return ans;
}
};