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;
    }
};
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值