您的位置:首页 > 产品设计 > UI/UE

Leetcode 303. Range Sum Query - Immutable & 307. Range Sum Query - Mutable

2016-05-26 05:28 686 查看


303. Range Sum Query - Immutable

Total Accepted: 46291 Total Submissions: 178615 Difficulty: Easy

Given an integer array nums, find the sum of the elements between indices i and j (i ≤ j), inclusive.
Example:

Given nums = [-2, 0, 3, -5, 2, -1]

sumRange(0, 2) -> 1
sumRange(2, 5) -> -1
sumRange(0, 5) -> -3


Note:

You may assume that the array does not change.
There are many calls to sumRange function.

Hide Company Tags
 Palantir

Hide Tags
 Dynamic Programming

Hide Similar Problems
 (M) Range Sum Query 2D - Immutable (M)
Range Sum Query - Mutable (M) Maximum Size Subarray Sum Equals k

两道蛮经典的设计题,而且并不简单。

首先是303,这道题mark是immutable,意思就是说我们给的数组给定之后就不会再变化了。这样就不会有之后的update的问题。

方法一:

正常计算。每次call都计算i~j的元素。TLE了。

这种题的点大概都在于看要求什么是O(1)。比如这个题,说了sumRange会有很多call,那意思就是sumRange的时间应该是最优化的。正常计算每一次的时间都是O(n),肯定不是最优化。

方法二:

用HashTable存结果。正如教程所说,初始化载入的时候就直接O(n^2)计算所有可能的结果,然后存入HashMap。这样浪费空间。有没有更好的方法?

方法三:

O(n)空间,O(1)时间。初始化一个sum数组,size是输入数组长度+1。然后初始化的时候就计算出来0~当前index-1元素的和存入sum[index]。这样求i~j只需要取sum[j+1]-sum[i]。

public class NumArray { // O(n) space, O(n) pre computation, O(1) access time
int[] sum;

public NumArray(int[] nums) {
this.sum = new int[nums.length+1];
for(int i=0; i<nums.length; i++){
sum[i+1] = sum[i] + nums[i];
}
}

public int sumRange(int i, int j) {
return sum[j+1]-sum[i];
}
}

// Your NumArray object will be instantiated and called as such:
// NumArray numArray = new NumArray(nums);
// numArray.sumRange(0, 1);
// numArray.sumRange(1, 2);




307. Range Sum Query - Mutable

Total Accepted: 20600 Total Submissions: 111220 Difficulty: Medium

Given an integer array nums, find the sum of the elements between indices i and j (i ≤ j), inclusive.
The update(i, val) function modifies nums by updating the element at index i to val.
Example:

Given nums = [1, 3, 5]

sumRange(0, 2) -> 9
update(1, 2)
sumRange(0, 2) -> 8


Note:

The array is only modifiable by the update function.
You may assume the number of calls to update and sumRange function is distributed evenly.

Hide Tags
 Segment Tree Binary
Indexed Tree

Hide Similar Problems
 (E) Range Sum Query - Immutable (H)
Range Sum Query 2D - Mutable

这道题其实并不简单。其实可以算hard题目了。因为需要一个特殊的数据结构来解决,从而既保证update高效,又保证sumRange高效。

博主也是临时学习的。

这道题的tag是segment tree和binary index tree。网上的资料很多,博主看了很多,将列出俩觉得最helpful的,并自己描述一些点。

首先segment tree和binary index tree的区别在哪?

segment tree正如教程所描述,根代表n个数字的和,然后左边是前半段,后面是后半段,然后如此反复往下,直到node的区间长度为1(这时为叶子节点)。这时会有正常二叉树的节点对应关系,对于i节点,左子树是2*i,右子树是2*i+1。建立这个树并不难,自底向上就行;更新一个节点也不难,自底向上。难的就是当给了一个区间,问sum的时候有4种case的判断,博主愚钝,看了一个小时,没看懂教程哪里LR和lr是怎么回事。

binary index tree则是一种简化版的segment tree。bit能干的事,seg一定能干,反之则不一定。但是bit简单,代码效率高,并且好理解。网上搜它们的区别可以知道虽然是同一个level的时间复杂度,但其实有差常数,所以bit更快。原理是巧妙的做了一个index的映射关系。使得我们通过一定的规律就可以获得想要的值。

A Binary Indexed Tree (BIT) is used to store cumulative sums. You have an array a0, a1, ..., an. You want to be able to retrieve the sum of the first k elements in O(logn) time, and you want to be able to add a quantity q to the i-th element in O(logn) time. Note that these two operations can be implemented with a normal array, but the time complexity will be O(n) and O(1). Tutorial here.

A segment tree (sometimes called range tree) is a much more flexible data structure, you can use it to store many different things. You have an array a0, a1, ..., an. You want to be able to retrieve the sum (or the maximum, or the minimum, or the greatest common divisor, or another associative function) of the elements between the l-th and the r-th in O(logn) time, and you want to be able to add (or to overwrite, or to multiply by...) a quantity q to the i-th element (or to every element between the l-th and the r-th) in O(logn) time.

Anything that can be done using a BIT can also be done using a segment tree : BIT stores cumulative quantities for certain intervals. Segment tree stores cumulative quantities for those intervals and more. In particular, if we are creating a data structure to deal with an array of size N=2^K, the BIT will have cumulative quantities for N intervals whereas the segment tree will have cumulative values for 2N-1 intervals

There are things that a segment tree can do but a BIT cannot : A BIT essentially works with cumulative quantities. When the cumulative quantity for interval [i..j] is required, it is found as the difference between cumulative quantities for [1...j] and [1...i-1]. This works only because addition has an inverse operation. You cannot do this if the operation is non-invertible (such as max). On the other hand, every interval on a segment tree can be found as union of disjoint intervals and no inverse operation is required

A BIT requires only half as much memory as a segment tree : In cases where you have masochistic memory constraints, you are almost stuck with using a BIT

Though BIT and segment tree operations are both O(log(n)), the segment tree operations have a larger constant factor : This should not matter for most cases. But once again, if you have masochistic time constraints, you might want to switch from a segment tree to a BIT. The constant factor might become more of a problem if the BIT/Segment tree is multidimensional.

With practice, coding either will be very fast : If you have coded a segment tree 100 times, you will get it very fast the next time you do it. So no need to worry about code being long.


博主的代码参考这里:https://www.zybuluo.com/Yano/note/320858

博主学习binary index tree时参考这里,非常清晰:http://www.cnblogs.com/xudong-bupt/p/3484080.html

代码的重点就俩:

1. 初始化时:比如给了data[1]这个位置元素,那首先data[1]是放在helper的2号index的(前缀和,helper这个数组比data数组长度大1)。然后pos += lowBit(pos);这句话什么意思呢,就是说这些位置的节点结果中也包含了当前元素。所以代码的意思是,取出第一个数据元素,依次将其放入有关系的节点中,然后取第二个数据元素,放入与其有关系的节点中...

2. 取sum时:sum[i],首先去index = i,然后i-=lowBit(i),当i>0往复。如果数字 i 的二进制表示中末尾有k个连续的0,则helper[i]是a数组中2^k个元素的和。

博主看来,这种神奇的对应关系,利用lowbit实现了,所以这方法叫binary index tree,因为神奇的利用low bit做了关联节点的映射和关联。参考链接2中原文用的是后继前继,不妨理解为父节点和子节点(子节点的比喻稍微不是100%合适)。

那么:

i-=lowBit(i) 这句话就等于从父节点往下找子节点。

pos += lowBit(pos) 这句话则是从子节点往上找父节点,所以同一个元素,这些父节点在数组中的位置都要加入该值(初始化时)。

public class NumArray {
// array存储 nums数组,helper用来维护array的前缀和
int[] array, helper;
public NumArray(int[] nums) {
array = new int[nums.length];
helper = new int[nums.length + 1];
for (int i = 0; i < nums.length; i++) {
array[i] = nums[i];
}
for (int i = 0; i < nums.length; i++) {
add(i + 1, nums[i]);
}
}
private void add(int pos, int value) {
while (pos < helper.length) {
helper[pos] += value;
pos += lowBit(pos);
}
}
// 预备函数,返回参数转为二进制后,最后一个1的位置所代表的数值
private int lowBit(int pos) {
return pos & (-pos);
}
private int sum(int pos) {
int rt = 0;
while (pos > 0) {
rt += helper[pos];
pos -= lowBit(pos);
}
return rt;
}
void update(int i, int val) {
int delta = val - array[i];
array[i] = val;
add(i + 1, delta);
}
public int sumRange(int i, int j) {
return sum(j + 1) - sum(i);
}
}

// 初始化复杂度最优为O(Nlog N)
// 单次询问复杂度O(log N)
// 单次修改复杂度O(log N)
// 空间复杂度O(N)

// Your NumArray object will be instantiated and called as such:
// NumArray numArray = new NumArray(nums);
// numArray.sumRange(0, 1);
// numArray.update(1, 10);
// numArray.sumRange(1, 2);
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息