307. Range Sum Query - Mutable

2024-02-14 09:40
文章标签 range sum 307 query mutable

本文主要是介绍307. Range Sum Query - Mutable,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

最后更新

四刷
09-Jan-2017

区间内频繁查找,更新。。

先用线段树(SegmentTree)来做,这个题几乎是把线段树的操作都用了一遍。

每个NODE只有4种可能
1)如果l-r包含了整个NODE,那么就是这个node;
2)如果l-r在整个NODE范围的外面,那么无视此node;
3)如果l-r在左边或者右边,选其中一边;
4)如果l-r同时覆盖左边右边,两边都要选。

Initiliaztion: O(n) 假如区间有N个元素,最多会有2N-1个node(not leaf),所以建树还是O(N).

Update: O(lgN)

最多只会有4个node被我们选取,而且还是底层,否则中间应该只有2个会被选:
image

你找出个多于4的情况我直播吃屎。
image

或者把它当做balanced binary tree..查找就和树的高度有关.

Query: O(lgN) 和update一样,都是定位问题。

Space: O(N) 来存整个

public class NumArray {public class SegmentTreeNode {int start;int end;int sum;SegmentTreeNode left;SegmentTreeNode right;public SegmentTreeNode(int start, int end, int sum) {this.start = start;this.end = end;this.sum = sum;this.left = this.right = null;}}SegmentTreeNode root;public SegmentTreeNode buildTree(int start, int end, int[] nums) {if (start > end) return null;if (start == end) return new SegmentTreeNode(start, start, nums[start]);int mid = start + (end - start) / 2;SegmentTreeNode leftChild = buildTree(start, mid, nums);SegmentTreeNode rightChild = buildTree(mid + 1, end, nums);SegmentTreeNode tempRoot = new SegmentTreeNode(start, end, leftChild.sum + rightChild.sum);tempRoot.left = leftChild;tempRoot.right = rightChild;return tempRoot;}public NumArray(int[] nums) {root = buildTree(0, nums.length - 1, nums);}void update(int i, int val) {updateSegmentTree(root, i, val);}void updateSegmentTree(SegmentTreeNode root, int i, int val) {if (root == null) return;if (i < root.start || i > root.end) return;if (root.start == i && root.end == i) {root.sum = val;} else {updateSegmentTree(root.left, i, val);updateSegmentTree(root.right, i, val);root.sum = root.left.sum + root.right.sum;}}public int sumRange(int i, int j) {return sum(root, i, j);}public int sum(SegmentTreeNode root, int start, int end) {if (root == null) return 0;if (end < root.start || start > root.end) return 0;int newStart = Math.max(root.start, start);int newEnd = Math.min(root.end, end);if (root.start == newStart && root.end == newEnd) return root.sum;return sum(root.left, newStart, newEnd) + sum(root.right, newStart, newEnd);}}

然后是树状数组的做法。。一会补上。

记住这么几个:

1) fenWickTree的index是从1,开始。
2) 往右上找是 index += (index & -index);
3) 往左上找是 index -= (index & -index);

这个题是用一种 变化 的思想,即使initialization都是看做从0变化为nit的值。

需要保存2 个array,一个是fenWickTree,另一个是当前nums[]的值,用于更新的时候知道某一个element变化了多少。

Time:

init: O(NlgN)
update, query: O(lgN)

public class NumArray {int[] fenWickTree;int[] nums;public NumArray(int[] nums) {this.fenWickTree = new int[nums.length + 1];this.nums = new int[nums.length];for (int i = 0; i < nums.length; i++) {update(i, nums[i]);}}void update(int i, int val) {int diff = val - nums[i];nums[i] = val;// j is the index in fenWickTree array, so +1int j = i + 1;while (j < fenWickTree.length) {fenWickTree[j] += diff;j += (j & -j);}}public int sum(int i) {int total = 0;int j = i + 1;while (j > 0) {total += fenWickTree[j];j -= (j & -j);}return total;}public int sumRange(int i, int j) {return sum(j) - sum(i-1);}
}

一刷

一开始很天真,用DP做
发现超时了。。

然后查资料发现==线段树==或者==树状数组==的应用。
我现在对那些发明这些结构的人,真是五体投地,我真心服。

先说树状数组吧

这个数据结构的原理比较复杂,我在那时没明白作者如何想出来的这个结构,只能解释下它是怎么作用的。

处理原数组index来建立一个类似于Tree的结构。

规律就是看index变成binary后 ==最右== ==不为0==的bit在哪一位。

indexbinaryright most non-zero bit
10 0 0 11
20 0 1 02
30 0 1 11
40 1 0 04
50 1 0 11
60 1 1 02
70 1 1 11
80 1 0 08
91 0 0 11
101 0 1 02

image

加粗的地方是不是很像BST

纵轴是最右不为0的BIT位
横轴是INDEX
可以看出是个TREE的结构 所以新结构我们定义成一个ARRAY就可以

然后我们怎么来利用呢

ARRAY里保存的数是其所有左边节点的value+自己的VALUE
newArray[4]保存的是nums[1]+nums[2]+nums[3]+nums[4]
而newArray[3]因为没有左子树,所以保存的只有nums[3]自己

所以当nums[n]变化的时候,他所有的父节点都变化。

比如我们要求nums[0] to nums[7]的和
分成3部分
newArray[7] + newArray[6] + newArray[4]
言外之意是把它当做右边的子节点,然后找父节点,父节点自动包含父节点左边所有的值,直到找不到父节点为止。

比如7的时候,父节点是6,然后6的父节点是4.
7包含nums[7]
6包含ums[6] nums[5]
4包含nums[4] 3 2 1

4作为右节点没有父节点了。

再比如我们需要求num[4] to nums[7], 就是4 5 6 7
newArray[7] = num[7]
newArray[6] = num[5] + num[6]
newArray[4] = num[4] + num[3] + num[2] + num[1]
这里7 6 4的顺序是从最大的7开始找左父节点找到的顺序.

最终结果就是(newArray[7] + newArray[6] + newArray[4]) - newArray[3]

那怎么找到所谓的父节点。

对于任意一个index,他的右父节点是 index + (index & -index)

我觉得这个记住比理解更便捷。。

如果它是右分支,那么他的左父节点是 index - ( index & -index)

比如途中的INDEX = 2,父节点是 2 + (2 & -2) 答案是4.

用刚才的7作为例子。

作为右节点
index = index - (index & -index)就能找到父节点
如果index < 0说明没了

作为左节点
index = index + (index & -index)就能找到父节点
如果index > newArray.length说明没父节点了

知道这些就可以有效利用了

需要注意的是,对于一个点,我们只能知道他的左父节点,或者右父借点,对于他的CHILDREN,我们无从得知,不管left child or Right child, both could be more than 1.

比较反社会的是,TREE里每个NODE的初始化,并不是比如tree[4]就寻找子节点,然后从1加4,而是只当做nums[4]从0变化到现在的num[4],因而更新整个右边的parent nodes.
以下图为例:
image

实际上初始的情况是从初始化tree[1]开始,tree[1],tree[2],tree[4],tree[8]都被更新了。
再初始化tree[2],然后tree[2],tree[4],tree[8]都被更新了。
再初始化tree[3],然后tree[3]tree[4],tree[8]都被更新了。
再初始化tree[4],然后tree[4],tree[8]都被更新了。
tree[5] = > tree[5], tree[6], tree[8]被更新。
....

public class NumArray 
{int[] nums;int[] tree;public NumArray(int[] nums) {this.nums = nums;this.tree = new int[nums.length+1];for(int i = 0; i < nums.length;i++){change(i+1,nums[i] - 0);        //all initialized as 0 in array, so its - 0. }}// nums[i] has been changed, so we need to change every father // node related to tree[i+1].(i+1 instead of i is simply becuase we set our tree// index starting with 1 not 0)public void change(int i, int val) {while(i < tree.length){tree[i] += val;i += (i & -i);}}public void update(int i, int val){change(i+1,val - nums[i]);nums[i] = val;}// starting from node i, add all its parent nodes up.(if there is any)//public int getSum(int i){int res = 0;while( i > 0){res += tree[i];i -= (i&-i);}return res;}public int sumRange(int i, int j) {return getSum(j+1) - getSum(i);}
}

INDEX对应从1开始,不是0,所以要注意。
很巧妙
初始化是n logn
查询是logn
更新是logn

很巧妙的数据结构...五体投地


转载于:https://www.cnblogs.com/reboot329/p/5878221.html

这篇关于307. Range Sum Query - Mutable的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/708139

相关文章

最大流=最小割=最小点权覆盖集=sum-最大点权独立集

二分图最小点覆盖和最大独立集都可以转化为最大匹配求解。 在这个基础上,把每个点赋予一个非负的权值,这两个问题就转化为:二分图最小点权覆盖和二分图最大点权独立集。   二分图最小点权覆盖     从x或者y集合中选取一些点,使这些点覆盖所有的边,并且选出来的点的权值尽可能小。 建模:     原二分图中的边(u,v)替换为容量为INF的有向边(u,v),设立源点s和汇点t

如何导入sun.misc.BASE64Encoder和sum.misc.BASE64Decoder

右击项目名--->Build Path--->Configure Build Path...--->java Build Path--->Access rules:1 rule defined,added to all librar...   --->Edit --->Add...

C++中的mutable关键字详解

目录 1.概述 2.使用场景 3.示例 4.mutable修饰Lambda表达式 5.注意事项 1.概述         在C++中,mutable也是为了突破const的限制而设置的。被mutable修饰的变量,将永远处于可变的状态,即使在一个const函数中。         我们知道,被const关键字修饰的函数的一个重要作用就是为了能够保护类中的成员变量。即:该函数可以

18. 4 Sum

题目: 解答: 与之前的三数之和的解法类似,也是先排序,然后不断剔除不可能的条件,最后两个参数,通过两头求和计算得出。 代码: class Solution {public:vector<vector<int>> fourSum(vector<int>& nums, int target) {vector<vector<int>> result;int len = nums.size

构建现代API:FastAPI中Query与Body参数的最佳搭配

在FastAPI中,Query 和 Body 是两种不同的依赖注入器,它们的应用场景取决于你的具体需求。以下是它们各自常见的使用场景: Query 参数 使用场景: 当你需要从URL中获取一些简单的参数时,例如过滤、排序、分页等。 当数据量不大,且可以作为URL的一部分安全传输时。当数据不需要复杂的结构时。 Body 参数 使用场景: 当你需要发送较为复杂的数据结构时,例如包含多个字段

apt-get update更新源时,出现“Hash Sum mismatch”问题

转载自:apt-get update更新源时,出现“Hash Sum mismatch”问题 当使用apt-get update更新源时,出现下面“Hash Sum mismatch”的报错,具体如下: root@localhost:~# apt-get update ...... ...... W: Failed to fetch http://us.archive.ubuntu.com/ub

MySql 1264 - Out of range value for column 异常

前段时间操作数据库,本是一个很简单的修改语句,却报了  1264 - Out of range value for column字段类型官网  当时一看懵逼了,网上很多都说是配置的问题,需要修改my.ini文件,这个方式我没有试过,我想肯定还有其它方法,经过慢慢排 查发现表里的字段为 decimal(10,3) ,这说明小数点前只有7位,保留了3位小数点,而值在小数点前却有8位,这就导致了错误

【硬刚ES】ES基础(二十) 单字符串多字段查询:Dis Max Query

本文是对《【硬刚大数据之学习路线篇】从零到大数据专家的学习指南(全面升级版)》的ES部分补充。

【硬刚ES】ES基础(十九) Query Filtering 与多字符串多字段查询

本文是对《【硬刚大数据之学习路线篇】从零到大数据专家的学习指南(全面升级版)》的ES部分补充。

ON_COMMAND_RANGE 和 ON_UPDATE_COMMAND_UI_RANGE

 ON_COMMAND_RANGE 和 ON_UPDATE_COMMAND_UI_RANGE 可以影射ID连续的Toolbar/Menu ID。 ON_COMMAND_RANGE影射的消息响应函数需要一个参数UINT表明是哪一个消息, afx_msg void OnZoom(UINT nID); 而ON_UPDATE_COMMAND_UI_RANGE的消息响应函数则无此ID,与ON