315. Count of Smaller Numbers After Self
这道题的一道面经variant:input是两个数组,假设n个人站成一队,每个人都只能知道前面有多少个人比自己高,并且知道所有人的身高,求这些人index站队的顺序。
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.
方法0:insertion sort(brute force)
思路:
最直观的思路就是从右往左数,并每次统计右边更小的数字cnt,这个时间复杂度是O(n^2)。稍加优化,如果我们新建一个数组,并将遇到过的数字按照从小到大排序,那么遇到新的数字就可以直接找到lower_bound的位置插入。这就是insertion sort的方法。在复杂度上,如果完全降序排列还是会达到O(n ^2)。
方法1:BST
grandyang:https://www.cnblogs.com/grandyang/p/5078490.html
discussion:https://leetcode.com/problems/count-of-smaller-numbers-after-self/discuss/76580/9ms-short-Java-BST-solution-get-answer-when-building-BST
思路:
那么上面的方法可以怎样优化呢?我们想去掉重复cnt的过程,那么可以用一个augmented bst来keep数字的相对大小,每一次我们利用bst的性质减少确定insertion位置的复杂度。但同时还要在不继续traverse的情况下直接获知自己的rank,只能将bst augment出一个smallercnt的变量,其含义为比自己小的数字个数(not exactly to date? 向左子树插入的时候除了根节点整个右子树都没有被通知到?but doesn’t matter perhaps)这样在遇到比自己小的数字时,更小的数字就可以直接跳过了。虽然worst case,在[n, n - 1, n - 2, … 1]全部递减的时候复杂度仍然是O(n^2),因为这里的bst是不平衡的。
本质上仍然是insertion sort。从右向左遍历nums,在bst中寻找新数字应该插入的位置,而bst可以被augmented来储存smallerCn: 有多少比当前节点要小的数字。如果root -> val > val,要将root -> smallerCnt++,并递归向左子树调用。如果root -> val <= val , 递归调用右子树,并累加沿途经过的所有root -> smallerCnt。注意还要判断如果root -> val == val,举个栗子,[4,5,2,6,7,4,3],在插入最后一个4的的时候,会建立一个新的node,被插入第一个4的右子树,但不能将smallcnt++。
class Solution {
private:
struct TreeNode {
TreeNode* left, * right;
int val, smallerCnt;
TreeNode (int _val, int _sm) {
val = _val;
smallerCnt = _sm;
left = nullptr;
right = nullptr;
}
};
TreeNode* insert(TreeNode* root, int val, int idx, int presum, vector<int> & res) {
if (!root) {
TreeNode* node = new TreeNode(val, 0);
res[idx] = presum;
return node;
}
else if (root -> val > val) {
root -> smallerCnt++;
root -> left = insert(root -> left, val, idx, presum, res);
}
else {
// 至关重要的双括号
root -> right = insert(root -> right, val, idx, presum + root -> smallerCnt + ((root -> val < val) ? 1 : 0), res);
}
return root;
}
public:
vector<int> countSmaller(vector<int>& nums) {
int n = nums.size();
vector<int> res(n, 0);
TreeNode* root = nullptr;
for (int i = n - 1; i >= 0; i--) {
root = insert(root, nums[i], i, 0, res);
}
return res;
}
};
方法2:BIT/Fenwick Tree
思路:
那么如果我们知道了每一个新进来的数字的rank,我们可以把这个rank变形成为一个fenwich tree,相当于记录一个以rank为index,count为number的vector,而fenwich记录的就是这一个数组的presum。这样就使得在logn的时间内可以get/update count信息。
用plain number我们使用O(1)时间update,O(n)时间getsum;用presum我们用O(n)时间update,O(1)时间getsum。fenwich tree达到了get/update都只用O(logn)。
// Time complexity: O(nlogn)
// Space complexity: O(k), k is the number unique elements
class FenwickTree {
public:
FenwickTree(int n): sums_(n + 1, 0) {}
void update(int i, int delta) {
while (i < sums_.size()) {
sums_[i] += delta;
i += lowbit(i);
}
}
int query(int i) const {
int sum = 0;
while (i > 0) {
sum += sums_[i];
i -= lowbit(i);
}
return sum;
}
private:
static inline int lowbit(int x) { return x & (-x); }
vector<int> sums_;
};
class Solution {
public:
vector<int> countSmaller(vector<int>& nums) {
// Sort the unique numbers
set<int> sorted(nums.begin(), nums.end());
// Map the number to its rank
unordered_map<int, int> ranks;
int rank = 0;
for (const int num : sorted)
ranks[num] = ++rank;
vector<int> ans;
FenwickTree tree(ranks.size());
// Scan the numbers in reversed order
for (int i = nums.size() - 1; i >= 0; --i) {
// Chechk how many numbers are smaller than the current number.
ans.push_back(tree.query(ranks[nums[i]] - 1));
// Increse the count of the rank of current number.
tree.update(ranks[nums[i]], 1);
}
std::reverse(ans.begin(), ans.end());
return ans;
}
};