[Week 2] LeetCode 335. Count of Smaller Numbers After Self
LeetCode 335. Count of Smaller Numbers After Self
問題描述:
You are given an integer array nums and you have to return a new counts array. The counts array has the property where counts[i]
is the number of smaller elements to the right of nums[i]
.
Example:
Input: [5,2,6,1]
Output: [2,1,1,0]
Explanation:
To the right of 5 there are 2 smaller elements (2 and 1).
To the right of 2 there is only 1 smaller element (1).
To the right of 6 there is 1 smaller element (1).
To the right of 1 there is 0 smaller element.
題解:
我們可以採用類似 MergeSort 的演算法來解決這個問題,按照 Example 裡給的資料 [5, 2, 6, 1] 來一步步分析:
[5, 2, 6, 1] = [5, 2] + [6, 1]
[5, 2] = [5] + [2] | [6, 1] = [6] + [1]
將 [5], [2] 合併成長度為2的陣列:
- 2 < 5, 則新陣列[2, *], 所有已經被推入新陣列的右陣列元素[2]
- 右陣列已空,則新陣列[2, 5], 比該元素小的右陣列元素有[2],故有5(1),代表比5小的元素有1個
將 [6], [1] 合併成長度為2的陣列:
1 < 6, 則新陣列[1, *], 所有已經被推入新陣列的右陣列元素[1]
右陣列已空,則新陣列[1, 6], 比該元素小的右陣列元素有[1],故有6(1),代表比6小的元素有1個
將 [2, 5], [1, 6] 合併成長度為4的陣列:
1 < 2, 則新陣列[1, *, *, *], 所有已經被推入新陣列的右陣列元素[1]
2 < 6, 則新陣列[1, 2, *, *], 比該元素小的右陣列元素有[1],故有2(1),代表比2小的元素有1個
5 < 6, 則新陣列[1, 2, 5, *], 比該元素小的右陣列元素有[1],故有5(1 + 1),代表比5小的元素有2個
左陣列已空,則新陣列[1, 2, 5, 6]
Ok, 目前我們已經算出輸入 [5, 2, 6, 1] 的輸出 [2, 1, 1, 0]。總結一下就是,記錄 MergeSort 每次合併過程中右陣列已被推入新陣列的元素(但其實我們只需要維護一個 helpCount 變數來記錄個數即可),當左陣列中有元素推入,則其所維護的統計量(比它小的元素個數)加上 helpCount。
Code:
class Solution {
public:
vector<int> countSmaller(vector<int> &nums)
{
vector<int> indices(nums.size(), 0);
vector<int> result(nums.size(), 0);
for (int i = 0; i < nums.size(); ++i)
indices[i] = i;
helper(nums, indices, result, 0, nums.size() - 1);
return result;
}
private:
void helper(vector<int> &nums, vector<int> &indices,
vector<int> &result, int low, int high)
{
if (low >= high)
return;
int mid = (low + high) / 2;
helper(nums, indices, result, low, mid);
helper(nums, indices, result, mid + 1, high);
int i = low, j = mid + 1, helpCount = 0, k = 0;
vector<int> tmp_nums(high - low + 1), tmp_indices(indices.begin(), indices.end());
while (i <= mid && j <= high)
{
if (nums[i] <= nums[j])
{
tmp_nums[k] = nums[i];
tmp_indices[low + k] = indices[i];
result[indices[i]] += helpCount;
++i;
}
else
{
tmp_nums[k] = nums[j];
tmp_indices[low + k] = indices[j];
++helpCount;
++j;
}
++k;
}
while (i <= mid)
{
tmp_nums[k] = nums[i];
tmp_indices[low + k] = indices[i];
result[indices[i]] += helpCount;
++i;
++k;
}
while (j <= high)
{
tmp_nums[k] = nums[j];
tmp_indices[low + k] = indices[j];
++helpCount;
++j;
++k;
}
for (i = low, j = 0; i <= high; ++i, ++j)
nums[i] = tmp_nums[j];
indices.assign(tmp_indices.begin(), tmp_indices.end());
}
};
複雜度分析:
將問題分解成求兩個小陣列(左半陣列和右半陣列,元素個數為 n/2 )的 Count of Smaller Numbers After Self 問題,求完後再在合併過程中求整陣列的 Count of Smaller Numbers After Self 問題,而兩個小問題又能各自分解成兩個小問題,依此遞迴直至陣列元素個數為1。
故我們能寫出表示式:T(n) = 2T(n/2) + O(n)
根據大師定理得:a = 2, b = 2, d = 1 即 d = logab,所以表示式複雜度為 O(nlogn)
Ok,如果對大師定理不瞭解,我們可以這樣分析,將一個元素個數為n的陣列每次折半,需要幾次能將陣列大小折為1呢,答案是 log2n 對吧!每一層陣列元素個數為上一層的一半(第1層除外)則每一層我們需要遍歷陣列幾次呢,答案是一次,例如[5],[2],[6],[1],要將它們變為長度為2的兩個陣列,需要遍歷這四個陣列各一次,所以加起來就是原陣列的長度。
其它解法:
- 維護二叉搜尋樹,將陣列的元素從左到右依次插入樹中,如果元素比root小,則root的統計量+1,然後進入左子樹,不斷遞迴到葉子節點…
- 使用STL的 upper_bound 和 lower_bound 函式…
- …