307. Range Sum Query - Mutable


題目:

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:

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

鏈接: http://leetcode.com/problems/range-sum-query-mutable/ 

題解:

這應該算是Range Query的經典題目之一了。也是通過這道題我第一次接觸到了Segment Tree,也對Fenwick Tree有了一點了解。下面是用Segment Tree來做的。 Segment Tree線段樹每一個節點都是一段線段,有start和end,然后還可以有其他的值,比如區間和sum,區間最大值max,區間最小值min。我們可以用自底向上構建二叉樹的方式構建Segment Tree,這個過程也有點類似於Bottom-up的merge sort,思想也是Divide and Conquer。完畢之后就可以在O(logn)的時間update,或者得到range Sum。其實更好的方法是使用Fenwick Tree, Fenwick Tree(Binary Indexed Tree)在處理Range Query真的是一絕,構造簡練,原理也精妙,還可以擴展到多維,一定要好好學一學。

Time Complexity - O(n) build, O(logn) update, O(logn) rangeSum,  Space Complexity - O(n)

public class NumArray {
    private class SegmentTreeNode {
        public int start;
        public int end;
        public int sum;
        public SegmentTreeNode left, right;
        public SegmentTreeNode(int start, int end) {
            this.start = start;
            this.end = end;
            this.sum = 0;
        }
    }
    
    private SegmentTreeNode root;
    
    public NumArray(int[] nums) {
        this.root = buildTree(nums, 0, nums.length - 1);    
    }

    public void update(int i, int val) {
        update(root, i, val);
    }
    
    private void update(SegmentTreeNode node, int position, int val) {
        if(node.start == position && node.end == position) {
            node.sum = val;
            return;
        }
        int mid = node.start + (node.end - node.start) / 2;
        if(position <= mid) {
            update(node.left, position, val);
        } else {
            update(node.right, position, val);
        }
        node.sum = node.left.sum + node.right.sum;
    }

    public int sumRange(int i, int j) {
        return sumRange(root, i, j);
    }
    
    private int sumRange(SegmentTreeNode node, int lo, int hi) {
        if(node.start == lo && node.end == hi) {
            return node.sum;
        }
        int mid = node.start + (node.end - node.start) / 2;
        if(hi <= mid) {
            return sumRange(node.left, lo, hi);
        } else if (lo > mid) {
            return sumRange(node.right, lo, hi);
        } else {
            return sumRange(node.left, lo, mid) + sumRange(node.right, mid + 1, hi);
        }
    }
    
    private SegmentTreeNode buildTree(int[] nums, int lo, int hi) {
        if(lo > hi) {
            return null;
        } else {
            SegmentTreeNode node = new SegmentTreeNode(lo, hi);
            if(lo == hi) {
                node.sum = nums[lo];
            } else {
                int mid = lo + (hi - lo) / 2;
                node.left = buildTree(nums, lo, mid);
                node.right = buildTree(nums, mid + 1, hi);
                node.sum = node.left.sum + node.right.sum;
            }
            return node;
        }
    }
}


// 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);

 

Fenwick Tree:  (Binary Indexed Tree) (樹狀數組) 

很有意思的構建,以數組nums = {1, 2, 3, 4, 5, 6, 7, 8}為例,這個數組長度為8。 跟dynamic programming的預處理很像,我們先建立一個長度為nums.length + 1 = 9的數組BIT。接下來遍歷數組nums,對BIT數組進行update(i + 1, nums[i])。這里BIT數組每個值BIT[i]代表nums數組里在i之前的部分元素和。原理是像自然數可以被表示為2n的和一樣,把nums數組里到0到i的sum表示成2n的和,從而導致update和rangeSum都可以用O(logn)的時間求出來。這里構建的時候可以有幾種寫法,主要就是利用當前i的least significante 1來確定到底BIT[i]要保存多少原數組的值。這里借用algorithmist的原話"Every index in the cumulative sum array, say i, is responsible for the cumulative sum from the index i to (i - (1<<r) + 1)。" 構建過程中可以用 (i & -i)來找到least significate 1,之后來進行i = i + (i & -i)來嘗試從小到大計算下一個BIT數組中被影響的元素。 而rangeSum的時候則使用i = i - (i & -i)來從大到小查找從0到i - 1的sum。

構建過程 - update, 給定數組nums = {1,2, 3, 4, 5, 6, 7, 8}

BIT[0] = 0

BIT[1] = nums[0] = 1 = 1

BIT[2] = nums[0] + nums[1] = 1 + 2 = 3

BIT[3] = nums[2] = 3 = 3

BIT[4] = nums[0] + nums[1] + nums[2] + nums[3] = 1+ 2 + 3 + 4 = 10

BIT[5] = nums[4] = 5 = 5

BIT[6] = nums[4] + nums[5] = 5 + 6 = 11

BIT[7] = nums[6] = 7 = 7

BIT[8] = nums[0] + nums[1] + nums[2] + nums[3] + nums[4] + nums[5] + nums[6] + nums[7] = 1 + 2 + 3 + 4 + 5 + 6 + 7 + 8 = 36

 

求Sum過程, 通過 sum = BIT[i + 1];   i = i - (i & -i);  從大到小迭代來計算。

sum(0) = BIT[1]

sum(1) = BIT[2]

sum(2) = BIT[3] + BIT[2]

sum(3) = BIT[4]

sum(4) = BIT[5] + BIT[4]

sum(5) = BIT[6] + BIT[4]

sum(6) = BIT[7] + BIT[6] + BIT[4] 

sum(7) = BIT[8]

 

得到sum(i)以后就可以相減來計算range sum了。

Time Complexity - O(nlogn) build,  O(logn) update, O(logn) rangeSum,  Space Complexity - O(n) 

public class NumArray {
    private int BIT[];               // Binary Indexed Tree = Fenwick Tree
    private int[] nums;
    
    public NumArray(int[] nums) {
        BIT = new int[nums.length + 1];
        for(int i = 0; i < nums.length; i++) {
            init(i + 1, nums[i]);
        }
        this.nums = nums;
    }

    private void init(int i, int val) {
        while(i < BIT.length) {
            BIT[i] += val;
            i = i + (i & -i);
        }
    }
    
    public void update(int i, int val) {
        int delta = val - nums[i];
        nums[i] = val;
        init(i + 1, delta);
    }

    public int sumRange(int i, int j) {
        return getSum(j + 1) - getSum(i);
    }
    
    private int getSum(int i) {
        int sum = 0;
        while(i > 0) {
            sum += BIT[i];
            i = i - (i & -i);
        }
        return sum;
    }
}

 

 

題外話: 今天去NYU圖書館自習,正值考期,人山人海的。我帶了電腦卻忘記帶插座,無奈只能用Java 在線的編譯器https://www.compilejava.net/, 不過這個真的還挺好用,除了不能debug,其他都可以,nice。晚上吃了Hakata Tonton,4個人大概人均$50+,並沒有想象的那么好吃,以后還是要攢機會去日本玩。 刷題群里大家對Heapify有了熱烈的討論,我自己認為Heapify主要有兩種,Bottom-up (swim)和Top-down(sink)。也要復習一下Priority Queue的implementation。要多看Sedgewick的課件和sample code才行。 在GitHub發現有個人叫indy256,實現了好多好多高級數據結構,有2d-fenwick tree,以及O(n)的Suffix Tree。大牛和普通人的距離真的好遙遠,我還是繼續努力。

 

二刷:

暫時只用了segment tree。  Fenwick Tree以后再理解。

Java:

Segment Tree:

public class NumArray {
    private SegmentTreeNode root;
    private int[] nums;
    
    public NumArray(int[] nums) {
        this.nums = nums;
        this.root = buildTree(0, nums.length - 1);
    }

    void update(int i, int val) {
        update(root, i, val);
    }
    
    private void update(SegmentTreeNode node, int pos, int val) {
        if (node == null) return;
        if (node.start == pos && node.end == pos) {
            node.val = val;
            nums[pos] = val;
            return;
        }
        int mid = node.start + (node.end - node.start) / 2;
        if (pos <= mid) {
            update(node.left, pos, val);
        } else {
            update(node.right, pos, val);
        }
        node.val = node.left.val + node.right.val;
    }

    public int sumRange(int i, int j) {
        return sumRange(root, i, j);
    }
    
    private int sumRange(SegmentTreeNode node, int lo, int hi) {
        if (lo > hi) return 0;
        if (node.start == lo && node.end == hi) return node.val;
        int mid = node.start + (node.end - node.start) / 2;
        if (hi <= mid) {
            return sumRange(node.left, lo, hi);
        } else if (lo > mid) {
            return sumRange(node.right, lo, hi);
        } else {
            return sumRange(node.left, lo, mid) + sumRange(node.right, mid + 1, hi);
        }
    }
    
    private SegmentTreeNode buildTree(int lo, int hi) {
        if (lo > hi) return null;
        SegmentTreeNode node = new SegmentTreeNode(lo, hi);
        if (lo == hi) {
            node.val = nums[lo];
        } else {
            int mid = lo + (hi - lo) / 2;
            node.left = buildTree(lo, mid);
            node.right = buildTree(mid + 1, hi);
            node.val = node.left.val + node.right.val;
        }
        return node;
    }
    
    private class SegmentTreeNode {
        int start;
        int end;
        int val;
        SegmentTreeNode left, right;
        
        public SegmentTreeNode(int start, int end) {
            this.start = start;
            this.end = end;
            this.val = 0;
        }
    }
}


// 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);

 

 

 

Reference:

https://www.compilejava.net/
http://algs4.cs.princeton.edu/93intersection/IntervalST.java.html
https://leetcode.com/discuss/70202/17-ms-java-solution-with-segment-tree
http://www.geeksforgeeks.org/segment-tree-set-1-sum-of-given-range/
http://algs4.cs.princeton.edu/99misc/SegmentTree.java.html/
https://leetcode.com/discuss/70272/solution-using-buckets-updating-for-query-the-worst-case-fast
https://leetcode.com/discuss/74222/java-using-binary-indexed-tree-with-clear-explanation
https://leetcode.com/discuss/70278/simple-recursive-java-solution
http://algs4.cs.princeton.edu/99misc/FenwickTree.java.html
https://web.stanford.edu/class/cs97si/03-data-structures.pdf
https://en.wikipedia.org/wiki/Fenwick_tree
http://algs4.cs.princeton.edu/99misc/FenwickTree.java.html
http://cs.nyu.edu/courses/spring14/CSCI-UA.0480-004/Lecture4.pdf
https://www.topcoder.com/community/data-science/data-science-tutorials/
http://www.algorithmist.com/index.php/Fenwick_tree
https://leetcode.com/discuss/70273/java-7ms-binary-index-tree-solution
https://leetcode.com/discuss/72658/java-solution-with-binary-indexed-tree-beats-81-95%25
https://leetcode.com/discuss/74222/java-using-binary-indexed-tree-with-clear-explanation
https://leetcode.com/discuss/70311/11-ms-java-binary-tree
https://leetcode.com/discuss/70191/share-my-c-solution-1700ms-using-tree-array
https://leetcode.com/discuss/70293/java-binary-indexed-tree

https://sites.google.com/site/indy256/algo_cpp/fenwick_tree

https://sites.google.com/site/indy256/algo/fenwick_tree_2d

http://www.cnblogs.com/grandyang/p/4985506.html

http://citeseerx.ist.psu.edu/viewdoc/download;jsessionid=A4ADC19B8D7E3A202944A808F5840D21?doi=10.1.1.14.8917&rep=rep1&type=pdf

http://arxiv.org/pdf/1311.6093v5.pdf

https://leetcode.com/discuss/72658/java-solution-with-binary-indexed-tree-beats-81-95%25


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM