315. 计算右侧小于当前元素的个数-7月11日

题目

315. 计算右侧小于当前元素的个数

思路总结

没有想出时间复杂度比n2的方法。外循环遍历一遍所有数组元素需要n次循环,内循环累计所有比当前遍历元素小的元素最坏情况也要循环n次。

以下是官方解答和一位网友的思路。

官方思路

从后往前循环遍历的过程中,把当前遍历的元素nums[i]对应的桶中的值+1,同时count[i]的值就是目前比nums[i]小的元素的桶中值之和。

主要需要解决两个问题:

1.nums数组中的取值不确定,如何为每个存在的值分配唯一的桶,即“离散化”。离散化的方法有很多,但是目的是一样的,即把原序列的值域映射到一个连续的整数区间,并保证它们的偏序关系不变。

2.动态维护前缀和(插入和查询),使两者操作的时间复杂度小于n。

第一点的解决方法可以是把nums数组去重排序,已知元素值,通过二分法来获取去重数组中对应的下标。已知下标访问去重数组得到元素值。(个人理解是这个去重数组就是一个映射工具)

第二点可以利用树状数组来解决。

网友思路

逆序遍历nums数组的所有元素,遍历到新的元素时,把它插入到一个有序链表或数组中(按从小到大排序)。那么插入时在新链表或数组中的序号就是比该元素小的元素个数。

但是好像这样复杂度始终是n2。因为如果插入到链表,那么插入时需要从头节点依次访问到合适的位置时间复杂度是n;如果插入到数组,在插入后,新插入元素之后的所有元素需要被复制移位一次,时间复杂度也是n。

实现

class Solution {
private:
    vector<int> c;
    vector<int> a;

    void Init(int length) {
        c.resize(length, 0);
    }

    int LowBit(int x) {
        return x & (-x);
    }

    void Update(int pos) {
        while (pos < c.size()) {
            c[pos] += 1;
            pos += LowBit(pos);
        }
    }

    int Query(int pos) {
        int ret = 0;

        while (pos > 0) {
            ret += c[pos];
            pos -= LowBit(pos);
        }

        return ret;
    }

    void Discretization(vector<int>& nums) {
        a.assign(nums.begin(), nums.end());
        sort(a.begin(), a.end());
        a.erase(unique(a.begin(), a.end()), a.end());
    }
    
    int getId(int x) {
        return lower_bound(a.begin(), a.end(), x) - a.begin() + 1;
    }
public:
    vector<int> countSmaller(vector<int>& nums) {
        vector<int> resultList;

        Discretization(nums);

        Init(nums.size() + 5);
        
        for (int i = (int)nums.size() - 1; i >= 0; --i) {
            int id = getId(nums[i]);
            resultList.push_back(Query(id - 1));
            Update(id);
        }

        reverse(resultList.begin(), resultList.end());

        return resultList;
    }
};

/**作者:LeetCode-Solution
链接:https://leetcode-cn.com/problems/count-of-smaller-numbers-after-self/solution/ji-suan-you-ce-xiao-yu-dang-qian-yuan-su-de-ge-s-7/
来源:力扣(LeetCode)
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。*/

拓展学习

树状数组

树状数组入门

解决的问题

 我们希望借助树状数组平衡了查询和更新的复杂度。

在上面讲的寻常方法中,1次更新或查询,要么复杂度为1要么为n。如果我们把前缀和分解为多个不同的2的幂的累加,那么在更新或者查询时,复杂度都变成了logn。

具体分解见下图。举例来说:如果查询1到11前缀累加和,那么只需要累加S8、S10、S11。如果要更新元素5的值,那么只需要更新S5、S6、S8、S16。

已知要更新的元素值,或者查询前缀长度,如何计算要被更新或者访问的数组下标?

已知查询前缀长度k,求前缀和。通过上面图示可以看到通过不断对二进制末尾的1进行取反操作可以得到下一次累加的下标。比如求S11,11的二进制编码是1011。那么需要累加的是C11(1011),C10(1010),C8(1000)。

一个高效而巧妙的方法lowbit(int m),作用是求出二进制数末尾为1的位置。为什么这样可以?因为负数在计算机中用补码存储,也就是对应正数所有位取反再+1。(0取反加1还是0,1取反加1还是1)

int lowbit(int m){
    return m&(-m);
}

求前缀和的代码可以如下

int ans = 0;
int getSum(int m){
    while(m > 0){
        ans += C[m];
        m -= lowbit(m);
    }
}

这样一来单次求前缀和的时间复杂度是logn。

那已知被更新的元素下标,如何更新辅助数组呢?即同时被更新的元素的下标是什么?

通过上面的图也可以看出,通过不断对二进制数末尾1的位置+1即得到下一个要更新的辅助数组中的下标(直到达到最大值n)。

更新元素值的代码如下

void update(int x, int value){
    A[x] += value;    //不能忘了对A数组进行维护,尽善尽美嘛
    while(x <= n){
        C[x] += value;
        x += lowbit(x);
    }
}

这里的时间复杂度同样是logn。

原文地址:https://www.cnblogs.com/BoysCryToo/p/13283227.html