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

说明:

  1. 数组仅可以在 update 函数下进行修改。

  2. 你可以假设 update 函数与 sumRange 函数的调用次数是均匀分布的。

答案:

 1class NumArray {
2    int[] tree;
3    int n;
4
5    public NumArray(int[] nums) {
6        n = nums.length;
7        tree = new int[n << 1];
8        buildTree(nums);
9    }
10
11    private void buildTree(int[] nums) {
12        for (int i = n; i < n << 1; i++) {
13            tree[i] = nums[i - n];
14        }
15        for (int i = n - 1; i > 0; i--) {
16            tree[i] = tree[i << 1] + tree[i << 1 | 1];
17        }
18    }
19
20    void update(int i, int val) {
21        for (tree[i += n] = val; i > 0; i >>= 1) {
22            tree[i >> 1] = tree[i] + tree[i ^ 1];
23        }
24    }
25
26    public int sumRange(int i, int j) {
27        int ret = 0;
28        for (i += n, j += n; i <= j; i >>= 1, j >>= 1) {
29            if ((i & 1) == 1)
30                ret += tree[i++];
31            if ((j & 1) == 0)
32                ret += tree[j--];
33        }
34        return ret;
35    }
36}

解析:

这题最简单解法是update函数直接替换数组的值,sumRange函数通过for循环计算求和,当然这种方法是最简单的,但当数据量比较大的时候就不合适了。就需要用到上面这种解法了,上面这种解法非常经典,经典到你都不一定能看懂,这里代码对位运算考察的还是比较多的,要理解这道题首先要搞懂什么叫线段树,大家可以先看一下下面的这张图

 

325,区域和检索 - 数组可修改_数组

数组nums中的数字全部都是线段树的叶子节点,第16行计算树的非叶子节点,非叶子节点的值等于他的左子树和右子树的和。第22行有个i^1的运算,他表示的是如果i是偶数则i^1就是奇数,如果i是奇数则i^1就是偶数。直接一点就是如果i是左子节点,则i^1就是右子节点,如果i是右字节点则i^1就是左子节点。sumRange函数中,因为i是小于j的,所以i是右子节点,j是左子节点才能计算,这里在第29行和31行都分别有判断(注意这里的tree[0]是没有储藏任何值的,tree下标为偶数的时候我们可以认为是左子节点,为奇数的时候可以认为他是右子节点)。这里第30行如果i是左子节点则不参与计算,下一步直接跳到他的父节点,如果i是右子节点则参与计算,然后下一步跳转到他的叔叔节点。同理第32行也一样。

比如有数组[1,3,5,7,9,11],我们构建的线段树如下

325,区域和检索 - 数组可修改_数组_02

方框内的是数组tree的下标,tree[0]是没有储藏任何值的,tree[1]储藏所有值的和,方框外是当前数组的值。

下面再来看一种解法

 1class SegmentTreeNode {
2    int start, end;
3    SegmentTreeNode left, right;
4    int sum;
5
6    public SegmentTreeNode(int start, int end) {
7        this.start = start;
8        this.end = end;
9        this.left = null;
10        this.right = null;
11        this.sum = 0;
12    }
13
14}
15
16class NumArray {
17    SegmentTreeNode root = null;
18
19    public NumArray(int[] nums) {
20        root = buildTree(nums, 0, nums.length - 1);
21    }
22
23    private SegmentTreeNode buildTree(int[] nums, int start, int end) {
24        if (start > end) {
25            return null;
26        } else {
27            SegmentTreeNode ret = new SegmentTreeNode(start, end);
28            if (start == end) {
29                ret.sum = nums[start];
30            } else {
31                int mid = start + (end - start) / 2;
32                ret.left = buildTree(nums, start, mid);
33                ret.right = buildTree(nums, mid + 1, end);
34                ret.sum = ret.left.sum + ret.right.sum;
35            }
36            return ret;
37        }
38    }
39
40    void update(int i, int val) {
41        update(root, i, val);
42    }
43
44    void update(SegmentTreeNode root, int pos, int val) {
45        if (root.start == root.end) {
46            root.sum = val;
47        } else {
48            int mid = root.start + (root.end - root.start) / 2;
49            if (pos <= mid) {
50                update(root.left, pos, val);
51            } else {
52                update(root.right, pos, val);
53            }
54            root.sum = root.left.sum + root.right.sum;
55        }
56    }
57
58    public int sumRange(int i, int j) {
59        return sumRange(root, i, j);
60    }
61
62    public int sumRange(SegmentTreeNode root, int start, int end) {
63        if (root.end == end && root.start == start) {
64            return root.sum;
65        } else {
66            int mid = root.start + (root.end - root.start) / 2;
67            if (end <= mid) {
68                return sumRange(root.left, start, end);
69            } else if (start >= mid + 1) {
70                return sumRange(root.right, start, end);
71            } else {
72                return sumRange(root.right, mid + 1, end) + sumRange(root.left, start, mid);
73            }
74        }
75    }
76}

这种使用递归的方式会更容易理解。下面再来看最后一种解法

 1public class NumArray {
2
3    int[] nums;
4    int[] BIT;
5    int n;
6
7    public NumArray(int[] nums) {
8        this.nums = nums;
9
10        n = nums.length;
11        BIT = new int[n + 1];
12        for (int i = 0; i < n; i++)
13            init(i, nums[i]);
14    }
15
16    public void init(int i, int val) {
17        i++;
18        while (i <= n) {
19            BIT[i] += val;
20            i += (i & -i);
21        }
22    }
23
24    void update(int i, int val) {
25        int diff = val - nums[i];
26        nums[i] = val;
27        init(i, diff);
28    }
29
30    public int getSum(int i) {
31        int sum = 0;
32        i++;
33        while (i > 0) {
34            sum += BIT[i];
35            i -= (i & -i);
36        }
37        return sum;
38    }
39
40    public int sumRange(int i, int j) {
41        return getSum(j) - getSum(i - 1);
42    }
43}