[LeetCode]-307. 区域和检索 - 数组可修改【线段树】

题目描述

给定一个整数数组  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)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值