题目描述
给定一个整数数组 nums,
求出数组从索引 i 到 j (i ≤ j) 范围内元素的总和
,包含 i, j 两点。
update(i, val) 函数可以通过将下标为 i 的数值更新为 val,从而对数列进行修改。
示例:
Given nums = [1, 3, 5]
sumRange(0, 2) -> 9
update(1, 2)
sumRange(0, 2) -> 8
说明:
数组仅可以在 update 函数下进行修改。
你可以假设 update 函数与 sumRange 函数的调用次数是均匀分布的。
来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/range-sum-query-mutable
[LeetCode]-303. 区域和检索 - 数组不可变
这是这道题的简单版本,大家可以先去看看这道题,那道题用了缓存法
本题有两种方法,(本人理解的两种方法)当然,方法更多,可在下方评论
方法一:直接修改
时间复杂度:O(1) 的更新查询 O(n)求和
对于区域和检索,我们从数组中访问每个元素的时间是固定的,在最坏的情况下,我们访问 n 元素。因此,时间复杂度为 O(n)。更新查询的时间复杂度O(1)
public int sumRange(int i, int j) {
int sum = 0;
for (int l = i; l <= j; l++) {
sum += data[l];
}
return sum;
}
public int update(int i, int val) {
nums[i] = val;
}
方法二:线段树
时间复杂度:O(logn)。算法的时间复杂度为O(logn),因为有几个树节点的范围包括 第 ii 数组元素,每个级别一个。有log(n)个级别。
空间复杂度:O(1)
第一步:构建线段树
线段树类似于完全二叉树,但是有些节点又只有一个左孩子或右孩子,为了构建完全二叉树以便于计算,可以自己加上一些虚节点,虚节点值设置为0
本例用数组来构建
//arr代表存放值的数组 tree是线段树 node代表当前节点
void build_tree(int arr[],int tree[],int node,int start,int end)
{
if(start!=end)
{
int mid = (start+end)/2; //将数组劈成两半 每次递归 不均匀的节点补上虚节点0
int left_node = 2*node+1; //左节点的索引
int right_node = 2*node+2; //右节点
//递归构建 直到start==end 只有一个值的时候tree[node] = arr[start] 或arr[end] 停止递归
build_tree(arr,tree,left_node,start,mid);
build_tree(arr,tree,right_node,mid+1,end);
tree[node] = tree[left_node]+tree[right_node]; //回溯 每个父节点的值为两个子节点之和
//有的父节点只有一个子节点 因为构建虚节点0 所以没有影响
}
else
{
tree[node] = arr[start];
}
}
第二步:求和
求和的话可以先判断求和范围是在左子树还是在右边
L R代表求和范围的起点 终点
看上图 有两种情况
1、求【4-5】的和 那么直接递归右边即可
2、求【1-5】呢 那就要先计算【4-5】递归右边 再计算【1】的值 最后相加
代码如下
int sumRange(int arr[],int tree[],int node,int start,int end,int L,int R)
{
if(R<start || L>end) //如果求和范围全在左边或者右边 直接计算一边即可 另一边返回0
{
return 0; //如果求和范围全在左边或者右边 直接计算一边即可 另一边返回0
}
else if(L<=start&&R>=end) //在L R中间可以直接返回预计算块的结果不用递归反复求和了
{
return tree[node];
}
else //如果求和范围还无法直接返回的 即在start end范围里面 递归左右子树
{
int mid = (start+end)/2;
int left_node = 2*node+1;
int right_node = 2*node+2;
int sum_left = sumRange(arr,tree,left_node,start,mid,L,R);
int sum_right = sumRange(arr,tree,right_node,mid+1,end,L,R);
return sum_left+sum_right;
}
}
第三步:更新
更新比较号理解 就是查找节点的过程
index代表更新的位置 val代表值
void update_tree(int arr[],int tree[],int node,int start,int end,int index,int val)
{
if(start==end)
{
arr[index] = val;
tree[node] = val;
}
else
{
int mid = (start+end)/2;
int left_node = 2*node+1;
int right_node = 2*node+2;
if(index >=start && index<=mid)
{
update_tree(arr,tree,left_node,start,mid,index,val);
}
else
{
update_tree(arr,tree,right_node,mid+1,end,index,val);
}
tree[node] = tree[left_node]+tree[right_node];
}
}
主函数
int arr[] = {1,3,5,7,9,11};
int size=6;
int tree[1000] = {0};
build_tree(arr,tree,0,0,size-1);
update_tree(arr,tree,0,0,size-1,4,6);
int s =sumRange(arr,tree,0,0,size-1,2,5);
cout<<s;
总结
线段树其实就是用了二分法加递归
如果不用线段树 将update时间复杂度变为O(1)那么求和就要重新再求了 变为O(n)