【LeetCode】0307. 区域和检索 - 数组可修改

今天是 Kevin 的算法之路100天征程的第4天。为大家讲解 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
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。

解题思路

方法一:暴力法

同样的,我们可以用暴力法直接刚,很简单粗暴,就不多解释了。

// Go
type NumArray struct {
	data []int
}

func Constructor(nums []int) NumArray {
	return NumArray{nums}
}

// 优秀的你应该发现了与上题相比,就是多了这个 Update 方法
func (this *NumArray) Update(i int, val int)  {
	this.data[i] = val
}

func (this *NumArray) SumRange(i int, j int) int {
	sum := 0
	for l := i; l <= j; l++ {
		sum += this.data[l]
	}
	return sum
}
// Java
class NumArray {

    private int[] data;

    public NumArray(int[] nums) {
        data = nums;
    }

    public int sumRange(int i, int j) {
        int sum = 0;
        for (int l = i; l <= j; l++) {
            sum += data[l];
        }
        return sum;
    }
    

    public void update(int i, int val) {
        data[i] = val;
    }
}

在上篇文章中已经说过暴力法最大的缺点就是耗时,上一题是通过缓存,以空间换时间来提升效率。那么这一题我们想想改怎么做呢?

建议大家先动脑筋想一想哦~ 是不是也可以用空间换时间呢?

答案是可以的

方法二:线段树(Segment Tree)

并且我们可以构造一种特殊的数据结构——线段树,线段树常用来处理数组相应的区间查询(range query)和元素更新(update)操作。线段树算是一种比较高级的树结构,这里作为了解即可。

这题使用的是树状数组。树状数组和线段树很像,但能用树状数组解决的问题,基本上都能用线段树解决,而线段树能解决的树状数组不一定能解决。相比较而言,树状数组效率要高很多。

相关介绍可以看下这个网站:https://oi-wiki.org/ds/fenwick/

代码实现

// go

type NumArray struct {
	s_tree, nums []int
}

func Constructor(nums []int) NumArray {
	newArray := &NumArray{make([]int, len(nums) + 1), nums}
	for i, num := range nums {
		newArray.s_update(i, num)
	}
	return *newArray
}

func (this *NumArray) Update(i int, val int)  {
	this.s_update(i, val - this.nums[i])
	this.nums[i] = val
}

func (this *NumArray) SumRange(i int, j int) int {
	return this.s_sum(j + 1) - this.s_sum(i)
}

func (this *NumArray) s_update(i, val int) {
  // 树状数组的标准写法,i & -i 是获取二进制的最小的一个1,然后用i减去这个1所在数字
	for i, n := i + 1, len(this.nums); i <= n; i += i & -i {
		this.s_tree[i] += val
	}
}

func (this *NumArray) s_sum(i int) (res int) {
	for ; i > 0; i -= i & -i {
		res += this.s_tree[i]
	}
	return
}

郑重声明:

所展示代码已通过 LeetCode 运行通过,请放心食用~

在唠唠嗑

很多人都想养成好习惯,但大多数人却是三分钟热度。为了我们能一起坚持下去,决定制定如下计划(福利)

一起学算法,打卡领红包!

参与方式:

关注我的原创微信公众号「Kevin的学堂」,一起学习,一起更优秀!

打卡规则为:

「留言」“打卡XXX天” ➕「分享」到朋友圈

奖励:

连续打卡 21 天,联系本人获取 6.6 元红包一个!

连续打卡 52 天,联系本人获取 16.6 元红包一个!

连续打卡 100 天,联系本人获取 66.6 元红包一个!

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值