[307]. 区域和检索 - 数组可修改
Posted Debroon
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了[307]. 区域和检索 - 数组可修改相关的知识,希望对你有一定的参考价值。
题目
题目:https://leetcode-cn.com/problems/range-sum-query-mutable/
函数原型
class NumArray
public:
NumArray(vector<int>& nums)
void update(int index, int val)
int sumRange(int left, int right)
;
线段树(区间树)
前置:线段树的具体介绍:https://blog.csdn.net/qq_41739364/article/details/121432200
对比 303(线段树部分),307 多了一步 update
,需要更新某个位置/区间的值,其他代码同。
void update(int index, int val)
data[index] = val; // 数组更新
// 线段树更新
update(0, 0, data.size()-1, index, val);
void update(int treeIndex, int l, int r, int index, int val)
if( l == r ) // 找到更新的节点了
tree[treeIndex] = val;
return;
int mid = l + (r - l) / 2;
int leftTreeIndex = leftchild(treeIndex); // 计算左孩子索引
int rightTreeIndex = rightchild(treeIndex); // 计算右孩子索引
if (index >= mid + 1) // 说明在右孩子中
update(rightTreeIndex, mid + 1, r, index, val); // 去右孩子找
else // 说明在左孩子中
update(leftTreeIndex, l, mid, index, val); // 去左孩子找
// 除了叶节点要更新,其实上面的父节点也得更新
tree[treeIndex] = tree[leftTreeIndex] + tree[rightTreeIndex];
完整代码:
class NumArray
public:
NumArray(vector<int>& arr)
if (arr.size() > 0)
for (auto v : arr)
data.push_back(v);
tree.resize(4 * data.size());
buildTree(0, 0, data.size() - 1); // 创建一个线段树
int sumRange(int i, int j)
if (data.size() == 0) return 0;
return query(0, 0, data.size() - 1, i, j); // 从根节点开始查询区间[i, j]和
public:
vector<int> data; // 数组,用于保存数据副本
vector<int> tree; // 线段树数组
// 返回完全儿叉树的数组表示中,一个索引所表示的元素的左孩子节点的索引(知道根节点,就知道左孩子)
int leftchild(int index)
return index * 2 + 1;
// 返回完全儿叉树的数组表示中,一个索引所表示的元素的右孩子节点的索引(知道根节点,就知道右孩子)
int rightchild(int index)
return index * 2 + 2;
// 在 treeIndex 位置创建表示区间 [l, r] 的线段树
void buildTree(int treeIndex, int l, int r)
if (l == r) // 结束条件:只有 1 个元素
tree[treeIndex] = data[l]; // 所存储的信息等于元素本身
return;
// 表示一个区间
int leftTreeIndex = leftchild(treeIndex); // 必定有左孩子
int rightTreeIndex = rightchild(treeIndex); // 相应的右孩子
// 创建左右子树相应的区间范围
int mid = l + (r - l) / 2; // 以 (l+r)/2 为中心
buildTree(leftTreeIndex, l, mid); // 区间 [l, mid] 创建线段树
buildTree(rightTreeIndex, mid+1, r); // 区间 [mid+1, r] 创建线段树
tree[treeIndex] = tree[leftTreeIndex] + tree[rightTreeIndex];
// 把左、右子树的值加在一起
/* 查询区间
- treeIndex 表示当前处理的线段树的节点,在 tree 这个数组的什么位置
- 在以 treeIndex 为根的线段树中 [l, r] 区间内,寻找 [queryL, queryR] 的值
*/
int query(int treeIndex, int l, int r, int queryL, int queryR)
if (l == queryL && r == queryR)
// 结束条件:左边界和想查询的 queryL 重合 且 右边界和想查询的 queryR 重合
return tree[treeIndex]; // 当前节点值是目标值
// 如果这个节点不是要找的区间,就需要到当前节点的孩子节点去找
int mid = l + (r - l) / 2;
int leftTreeIndex = leftchild(treeIndex); // 计算左孩子索引
int rightTreeIndex = rightchild(treeIndex); // 计算右孩子索引
if (queryL >= mid + 1) // 如果目标区间和左孩子无关
return query(rightTreeIndex, mid + 1, r, queryL, queryR); // 去右孩子找
else if (queryR <= mid) // 如果目标区间和右孩子无关
return query(leftTreeIndex, l, mid, queryL, queryR); // 去左孩子找
// 如果俩种情况都不是,目标区间没有完全落在孩子的区间中,那就需要进行拼接,俩边都需要找
// 从查找 [queryL, queryR] 变成查找[queryL, mid]、[mid + 1, queryR]
int leftresult = query(leftTreeIndex, l, mid, queryL, mid);
int rightresult = query(rightTreeIndex, mid + 1, r, mid + 1, queryR);
return leftresult + rightresult; // 融合
void update(int index, int val)
data[index] = val; // 数组更新
// 线段树更新
update(0, 0, data.size()-1, index, val);
void update(int treeIndex, int l, int r, int index, int val)
if( l == r ) // 找到更新的节点了
tree[treeIndex] = val;
return;
int mid = l + (r - l) / 2;
int leftTreeIndex = leftchild(treeIndex); // 计算左孩子索引
int rightTreeIndex = rightchild(treeIndex); // 计算右孩子索引
if (index >= mid + 1) // 说明在右孩子中
update(rightTreeIndex, mid + 1, r, index, val); // 去右孩子找
else // 说明在左孩子中
update(leftTreeIndex, l, mid, index, val); // 去左孩子找
// 除了叶节点要更新,其实上面的父节点也得更新,重新融合一次即可
tree[treeIndex] = tree[leftTreeIndex] + tree[rightTreeIndex];
;
update
函数不仅可以更新区间中某个值,也可以是更新区间的所有值。
我们按照上面的查询函数来更新区间即可,但其实我们还得更新下面的叶节点。
如果叶节点也更新,就是 O ( n ) O(n) O(n) 了。
于是,更好的是懒惰更新,维持 O ( l o g n ) O(log n) O(logn)。
- 更新区间后,还需要更新叶节点这一步
- 叶节点先不更新,我们创建一个
lazy
数组,记录未更新节点 - 直到再碰到这个节点时,把
lazy
数组中未更新的节点进行更新
【线段树拓展】
其实我们上面的线段树,是一维线段树:
还有二维线段树:
对于二维区间查询问题,也可以使用线段树。以此类推,三维、四维等都可以。
除此之外,还有动态线段树(链式),避免数组带来的浪费。
动态线段树,是根据需要关注的区间来动态创建:
SQRT 分解
对比 303(SQRT 分解部分),307 多了一步 update
,需要更新某个位置/区间的值,其他代码同。
SQRT 分解的单元素更新:
void update(int i, int val)
int b = i / B; // 先算出对应第几组
blocks[b] -= data[i]; // 组和 - 被更新的元素
blocks[b] += val; // 组合 + 更新元素
data[i] = val; // 单个元素更新,不能先更新,更新组合要用
完整代码:
class NumArray
vector<int> data;
vector<int> blocks; // 存储各组的和
int N; // 元素总数
int B; // 每组元素个数
int Bn; // 组数
public:
NumArray(vector<int>& nums)
N = nums.size();
if (N == 0)
return;
B = (int)sqrt(N);
Bn = N / B + (N % B > 0 ? 1 : 0); // 不能整除,组数+1
for(auto v : nums)
data.push_back(v);
blocks.assign(N, 0);
for(int i=0; i<N; i++)
blocks[i / B] += nums[i]; // 求组和
int sumRange(int left, int right)
int bstart = left / B; // 起始组号
int bend = right / B; // 终止组号
int res = 0;
if( bstart == bend ) // 所求的区间和属于同一组
for(int i=left; i<=right; i++) // 遍历得到结果
res += data[i];
return res;
// 所求的区间和不属于同一组,需要分成 3 组
int x = (bstart + 1) * B;
for(int i=left; i<x; i++) // 头组
res += data[i];
for(int i=bstart+1; i<bend; i++) // 中间
res += blocks[i];
for(int i=bend * B; i<=right; i ++) // 尾组
res += data[i];
return res;
void update(int i, int val)
int b = i / B; // 先算出对应第几组
blocks[b] -= data[i]; // 组和 - 被更新的元素
blocks[b] += val; // 组合 + 更新元素
data[i] = val; // 单个元素更新,不能先更新,更新组合要用
;
以上是关于[307]. 区域和检索 - 数组可修改的主要内容,如果未能解决你的问题,请参考以下文章