• 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。

  • 相关阅读:
    extjs 登录
    Digg 顶 踩(jQuery+Ajax)
    长度不能小于 0。 参数名: length
    .net 资料
    Micromedia Flash Player已终止一项可能不安全的操作解决办法
    SQL SERVER 与ACCESS、EXCEL的数据转换
    排名 sql
    FileUploaderAJAX 无刷新上传
    Microsoft Office Access ActiveX 部件不能创建对象
    流量分析作用
  • 原文地址:https://www.cnblogs.com/BoysCryToo/p/13283227.html
Copyright © 2020-2023  润新知