文章目录
前言
如果读者对于树状数组并没有了解,可以先阅读树状数组(一)这篇文章。本篇文章将进一步通过实战,对树状数组的知识点进行巩固。去尝试用树状数组去解决更多的问题。
一、最长上传前缀
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上的这些题目,相信你一定已经对树状数组有所了解,并且能使用树状数组去解决一些问题,加油!