树状数组(二)


前言

如果读者对于树状数组并没有了解,可以先阅读树状数组(一)这篇文章。本篇文章将进一步通过实战,对树状数组的知识点进行巩固。去尝试用树状数组去解决更多的问题。


一、最长上传前缀

1.1 题目链接

点击跳转到题目位置

1.2 题目描述

给你一个 n 个视频的上传序列,每个视频编号为 1 到 n 之间的 不同 数字,你需要依次将这些视频上传到服务器。请你实现一个数据结构,在上传的过程中计算 最长上传前缀 。

如果 闭区间 1 到 i 之间的视频全部都已经被上传到服务器,那么我们称 i 是上传前缀。最长上传前缀指的是符合定义的 i 中的 最大值 。

请你实现 LUPrefix 类:

  • LUPrefix(int n) 初始化一个 n 个视频的流对象。
  • void upload(int video) 上传 video 到服务器。
  • int longest() 返回上述定义的 最长上传前缀 的长度。

1.3 题目代码

class LUPrefix {
    int Fenwick[100010];
    int up;
    int lowbit(int x){
        return (-x)&x;
    }    

    bool judge(int x){
        int ans = 0;
        int n = x;
        while(n > 0){
            ans += Fenwick[n];
            n -= lowbit(n);
        }
        if(ans == x){
            return true;
        }
    return false;
    }

public:
    LUPrefix(int n) {
        memset(Fenwick, 0, sizeof(Fenwick));
        up = n;
    }
    
    void upload(int video) {
        while(video <= up){
            Fenwick[video]++;
            video += lowbit(video);
        }
        return ;
    }
    
    int longest() {
        int left = 1;
        int right = up;
        int ans = 0;
        while(left <= right){
            int mid = (left+right)>>1; 
            if(judge(mid) == true){
                ans = mid;
                left = mid+1;
            } else{
                right = mid-1;
            }
        }
    return ans;
    }
};

/**
 * Your LUPrefix object will be instantiated and called as such:
 * LUPrefix* obj = new LUPrefix(n);
 * obj->upload(video);
 * int param_2 = obj->longest();
 */

1.4 解题思路

(1) 用树状数组进行存储,用来统计前i个视频上传了多少个。

(2) 上传过程与树状数组中的基本操作一致。

(3) 如何判断前x视频全部上传用树状数组的查找进行统计,如果前x个视频的数量等于x则查找完毕。

(4) 最长上传前缀通过二分查找的方式进行查找。

二、 数组中的逆序对

2.1 题目链接

点击跳转到题目位置

2.2 题目描述

在数组中的两个数字,如果前面一个数字大于后面的数字,则这两个数字组成一个逆序对。输入一个数组,求出这个数组中的逆序对的总数。

2.3 题目代码

class Solution {
    unordered_map<int, int> index1;
    int lowbit(int x){
        return x & (-x);
    }
    
    void add(int *Fenwick, int x){
        while(x <= 50001){
            Fenwick[x]++;
            x += lowbit(x);
        }
    }

    int Find(int *Fenwick, int x){//
        int res = 0;
        while(x > 0){
            res += Fenwick[x];
            x -= lowbit(x);
        }
    return res;
    }

public:
    int reversePairs(vector<int>& nums) {
        int n = nums.size();
        int Fenwick[50010];
        vector<int> num = nums;
        memset(Fenwick, 0, sizeof(Fenwick));
        sort(nums.begin(), nums.end());
        int rank = 1;
        for(int i = 0; i < n; ++i){
            if(index1[nums[i]] == 0){
                index1[nums[i]] = rank;
                ++rank;
            }
        }
        int res = 0;
        for(int i = 0; i < n; ++i){
            rank = index1[num[i]];
            res += Find(Fenwick, rank);
            add(Fenwick, rank);
        }
    return (long long)n * (n-1) / 2 - res;
    }
};

2.4 解题思路

(1) 因为本题目的数据特别大,需要利用到离散化。离散化的思路简单而言,假设总共有8个数字,那树状数组中做多只需要开辟8个位置(那就开辟8个空位)<从1-8>(实际是9个,因为0位置不被需要)。那么将原来的数字进行排序,最小的放在第一个位置,最大的放在最后一个位置,这样依次从1往后,这样就能与树状数组联系在一起了。

(2) 我们查逆序对,只需要从左往右进行遍历,先找出当前左边小于等于该数字的个数,然后加入到树状数组中。最后统计出一个和为res。那么所有的数对数n * (n - 1) / 2 - res就是最终的结果了。

(3) 注意好数据范围即可,即有些地方需要添加上long long。

三、翻转对

3.1 题目链接

点击跳转到题目位置

3.2 题目描述

给定一个数组 nums ,如果 i < j 且 nums[i] > 2*nums[j] 我们就将 (i, j) 称作一个重要翻转对

你需要返回给定数组中的重要翻转对的数量。

3.3 题目代码

class Solution {
    unordered_map<long long, int> index1;
    int lowbit(int x){
        return x & (-x);
    }
    
    void add(int *Fenwick, int x){
        while(x <= 100010){
            Fenwick[x]++;
            x += lowbit(x);
        }
    }

    int find(int *Fenwick, int x){
        int res = 0;
        while(x > 0){
            res += Fenwick[x];
            x -= lowbit(x);
        }
    return res;
    }

public:
    int reversePairs(vector<int>& nums) {
        int n = nums.size();
        int Fenwick[100100];
        memset(Fenwick, 0, sizeof(Fenwick));
        vector<long long> num;
        for(int i = 0; i < n; ++i){
            num.push_back(nums[i]);
            num.push_back((long long)nums[i] * 2);
        }
        sort(num.begin(), num.end());
        int rank = 1;
        for(int i = 0; i < 2 * n; ++i){
            if(index1[num[i]] == 0){
                index1[num[i]] = rank;
                ++rank;
            }
        }
        int res  = 0;
        for(int i = 0; i < n; ++i){
            rank = index1[(long long)2 * nums[i]];
            int rank0 = index1[nums[i]];
            res += find(Fenwick, rank);
            add(Fenwick, rank0);
        }
    return (long long) n * (n-1) / 2  - res;
    }
};

3.4 解题思路

(1) 翻转对的思路与逆序对的思路一样,只不过在此道题目里面,所需要开辟的树状数组的内存是两倍。

(2) 采取一样的策略即可。当然同时也需要注意数据范围。

四、通过指令创建有序数组

4.1 题目链接

点击跳转到题目位置

4.2 题目描述

给你一个整数数组 instructions ,你需要根据 instructions 中的元素创建一个有序数组。一开始你有一个空的数组 nums ,你需要 从左到右 遍历 instructions 中的元素,将它们依次插入 nums 数组中。每一次插入操作的 代价 是以下两者的 较小值

  • nums 中 严格小于 instructions[i] 的数字数目
  • nums 中 严格大于 instructions[i] 的数字数目。

比方说,如果要将 3 插入到 nums = [1,2,3,5] ,那么插入操作的 代价 为 min(2, 1) (元素 1 和 2 小于 3 ,元素 5 大于 3 ),插入后 nums 变成 [1,2,3,3,5] 。

请你返回将 instructions 中所有元素依次插入 nums 后的 总最小代价 。由于答案会很大,请将它对 109 + 7 取余 后返回。

4.3 题目代码

class Solution {
    long long mod = 10e8 + 7;

    unordered_map<int, int> hash;

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

    void add(int *Fenwick, int x){
        while(x <= 100000){
            Fenwick[x]++;
            x += lowbit(x);
        }
    }
    
    int find(int *Fenwick, int x){
        int res = 0;
        while(x > 0){
            res += Fenwick[x];
            x -= lowbit(x);
        }
    return res;
    }

public:
    int createSortedArray(vector<int>& instructions) {
        int n = instructions.size();
        int Fenwick[100010];
        long long res = 0;
        memset(Fenwick, 0, sizeof(Fenwick));
        for(int i = 0; i < n; ++i){
            int num = find(Fenwick, instructions[i]);
            res =  (long long)(res + min(i - num, num - hash[instructions[i]])) % mod;
            add(Fenwick, instructions[i]);
            hash[instructions[i]]++;
        }
    return res;
    }
};

4.4 解题思路

(1) 这道题目实际上使用树状数组快速统计小于等于x的数字有多少个,然后遍历的同时用哈希表记录等于x的数字有多少个,同时又知道总的数字有多少个,这样就可以计算出严格小于和严格大于的数字的个数。这样做比较即可。

(2) 一定要注意数据范围,做好取余即可。

五、计算右侧小于当前元素的个数

5.1 题目链接

点击跳转到题目位置

5.2 题目描述

给你一个整数数组 nums ,按要求返回一个新数组 counts 。数组 counts 有该性质: counts[i] 的值是 nums[i] 右侧小于 nums[i] 的元素的数量。

5.3 题目代码

class Solution {
    int lowbit(int x){
        return x & (-x);
    }

    void add(int *Fenwick, int x){
        while(x <= 20001){
            Fenwick[x]++;
            x += lowbit(x);
        }
    }

    int find(int *Fenwick, int x){
        int res = 0; 
        --x;
        while(x > 0){
            res += Fenwick[x];
            x -= lowbit(x);
        }
    return res;
    }

public:
    vector<int> countSmaller(vector<int>& nums) {
        int Fenwick[20010];
        memset(Fenwick, 0, sizeof(Fenwick));
        int n = nums.size();
        vector<int> res(n);
        for(int i = n-1; i >= 0; --i){
            res[i] = find(Fenwick, nums[i] + 10000 + 1);
            add(Fenwick, nums[i] + 10000 + 1);
        }
    return res;
    }
};

5.4 解题思路

(1) 本道题目只需要从右往左遍历,套用之前树状数组的模板即可。本质上这些题目都是同种类型的题目。


总结

通过阅读本篇文章,尝试独立完成LeetCode上的这些题目,相信你一定已经对树状数组有所了解,并且能使用树状数组去解决一些问题,加油!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值