树状数组的用法总结
单点查询、区间修改,树状数组
tree的下标注意从1 到 n + 1,从0开始就死循环了
例题:
307. 区域和检索 - 数组可修改 - 力扣(LeetCode)
class NumArray { public: vector<int> c; vector<int> array; int n = 0; int lowbit(int x) { return x & (-x); } NumArray(vector<int>& nums) { n = nums.size(); c.resize(n + 1, 0); // 树状数组使用1-based indexing array.resize(n, 0); // 初始化数组 for (int i = 0; i < n; i++) { array[i] = nums[i]; } // 构建树状数组 for (int i = 0; i < n; i++) { add(i, nums[i]); } } void add(int index, int val) { // 转换为1-based indexing for (int i = index + 1; i <= n; i += lowbit(i)) { c[i] += val; } } int query(int x) { int ret = 0; // 转换为1-based indexing for (int i = x + 1; i > 0; i -= lowbit(i)) { ret += c[i]; } return ret; } void update(int index, int val) { int add_val = val - array[index]; add(index, add_val); array[index] = val; } int sumRange(int left, int right) { int ret = query(right); if (left > 0) ret -= query(left - 1); return ret; } }; /** * Your NumArray object will be instantiated and called as such: * NumArray* obj = new NumArray(nums); * obj->update(index,val); * int param_2 = obj->sumRange(left,right); */
逆序对

直接map映射的话也不需要处理想等元素问题
题目链接:315. 计算右侧小于当前元素的个数 - 力扣(LeetCode)
class Solution { public: int lowbit(int x) { return x & (-x); } void add(const int& x, const int& cnt, vector<int>& vis) { for (int i = x; i < cnt; i += lowbit(i)) { vis[i]++; } } int query(const int& x, const vector<int>& vis) { int sum = 0; for (int i = x; i > 0; i -= lowbit(i)) { sum += vis[i]; } return sum; } vector<int> countSmaller(vector<int>& nums) { int n = nums.size(); vector<int> vis; set<int> uq; map<int, int> mapp; for (int i = 0; i < n; i++) { uq.insert(nums[i]); } int cnt = 1; for (auto item : uq) { mapp[item] = cnt++; } vector<int> array; array.resize(cnt); vis.resize(cnt); vector<int> ret; for (int i = n - 1; i >= 0; i--) { int k = mapp[nums[i]]; ret.push_back(query(k - 1, vis)); add(k, cnt, vis); } std::reverse(ret.begin(), ret.end()); return ret; } };
题目链接:327. 区间和的个数 - 力扣(LeetCode)
第一步:理解问题转化
前缀和 + 树状数组(或线段树):
-
计算前缀和数组
prefix,其中prefix[k] = nums[0] + ... + nums[k-1](或类似定义)。 -
区间和
S(i, j)可以表示为prefix[j+1] - prefix[i]。 -
问题转化为:对于每个
j,统计有多少个i ≤ j使得lower ≤ prefix[j+1] - prefix[i] ≤ upper。 -
这等价于对于每个
j,找满足prefix[j+1] - upper ≤ prefix[i] ≤ prefix[j+1] - lower的i的个数。 -
可以使用树状数组或线段树来动态维护前缀和的值域,实现 O(log n) 的查询和更新,总复杂度 O(n log n)。
第二步:如何将每次查询优化到 O(log n)?核心思想
我们需要一种数据结构,它能动态地维护我们“已经遍历过的前缀和”(即所有
pre[i], i ≤ current_j),并支持以下两种操作:-
插入:当
j向后移动时,将新的pre[j]加入这个集合。 -
查询:给定一个数值范围
[L, R],快速返回当前集合中,值落在这个范围内的元素个数。
那就是可以用树状数组的一个用处:O(log n) 范围统计个数
第三步:离散化
这个问题使用离散化是因为前缀和的值范围非常大(可能达到约 n * 元素值 ≈ 10^10),无法直接用数组下标表示。离散化将这些大范围的数值映射到紧凑的整数下标
class Solution {
public:
int countRangeSum(vector<int>& nums, int lower, int upper) {
int n = nums.size();
if (n == 0) return 0;
// 1. 计算前缀和数组
vector<long long> prefix(n + 1, 0);
for (int i = 0; i < n; i++) {
prefix[i + 1] = prefix[i] + nums[i];
}
// 2. 收集所有需要离散化的值
set<long long> allNumbers;
for (int i = 0; i <= n; i++) {
allNumbers.insert(prefix[i]); // 前缀和本身
allNumbers.insert(prefix[i] - lower); // 查询右边界
allNumbers.insert(prefix[i] - upper); // 查询左边界
}
// 3. 离散化映射
unordered_map<long long, int> valToIdx;
int idx = 1;
for (long long num : allNumbers) {
valToIdx[num] = idx++;
}
// 4. 初始化树状数组
BIT bit(valToIdx.size() + 5);
// 5. 插入初始前缀和 pre[0] = 0
bit.add(valToIdx[0], 1);
// 6. 遍历并统计结果
int result = 0;
for (int j = 0; j < n; j++) {
// 计算当前查询的左右边界
long long left = prefix[j + 1] - upper;
long long right = prefix[j + 1] - lower;
// 查询在 [left, right] 范围内的前缀和个数
int leftIdx = valToIdx[left];
int rightIdx = valToIdx[right];
int count = bit.query(rightIdx) - bit.query(leftIdx - 1);
result += count;
// 插入当前前缀和
bit.add(valToIdx[prefix[j + 1]], 1);
}
return result;
}
private:
// 树状数组实现
class BIT {
private:
vector<int> tree;
int n;
public:
BIT(int size) : n(size), tree(size + 1, 0) {}
// 在位置 idx 增加 1
void add(int idx, int delta) {
while (idx <= n) {
tree[idx] += delta;
idx += idx & -idx;
}
}
// 查询前缀和 [1, idx]
int query(int idx) {
int sum = 0;
while (idx > 0) {
sum += tree[idx];
idx -= idx & -idx;
}
return sum;
}
};
};
自己选择的路,跪着也要走完。朋友们,虽然这个世界日益浮躁起来,只要能够为了当时纯粹的梦想和感动坚持努力下去,不管其它人怎么样,我们也能够保持自己的本色走下去。

浙公网安备 33010602011771号