Friday, August 11, 2017

307. Range Sum Query - Mutable

Version #3 Abstract Version of Segment Tree[TODO]
不建tree直接用int[] 表示tree
parent -> (n - 1) / 2
leftChild -> 2 * n + 1
rightChild -> 2 * n + 2

有空开心的时候写一下

三刷 07/2022
Version #2 Segment Tree
Time - constructor O(N), update O(logN), query O(logN)
Space O(N)
Runtime: 138 ms, faster than 61.04% of Java online submissions for Range Sum Query - Mutable.
Memory Usage: 133.4 MB, less than 34.89% of Java online submissions for Range Sum Query - Mutable.

class NumArray {
    private Node root;
    class Node {
        int start, end;
        int sum;
        Node left, right;
        public Node(int start, int end) {
            this.start = start;
            this.end = end;
        }
    }
    
    private Node buildTree(int[] nums, int start, int end) {
        Node curr = new Node(start, end);
        if (start == end) {
            curr.sum = nums[start];
            return curr;
        }
        int mid = start + (end - start) / 2;
        curr.left = buildTree(nums, start, mid);
        curr.right = buildTree(nums, mid + 1, end);
        curr.sum = curr.left.sum + curr.right.sum;
        return curr;
    }
    
    private void updateNum(Node node, int index, int val) {
        if (node.start == node.end && node.start == index) {
            node.sum = val;
            return;
        }
        int mid = node.start + (node.end - node.start) / 2;
        if (index <= mid) {
            updateNum(node.left, index, val);
        } else {
            updateNum(node.right, index, val);
        }
        node.sum = node.left.sum + node.right.sum;
    }
    
    private int query(Node node, int start, int end) {
        if (node.start == start && node.end == end) {
            return node.sum;
        }
        int mid = node.start + (node.end - node.start) / 2;
        if (start > mid) {
            return query(node.right, start, end);
        } else if (end <= mid) {
            return query(node.left, start, end);
        }
        return query(node.left, start, mid) + query(node.right, mid + 1, end);
    }

    public NumArray(int[] nums) {
        this.root = buildTree(nums, 0, nums.length - 1);
    }
    
    public void update(int index, int val) {
        updateNum(root, index, val);
    }
    
    public int sumRange(int left, int right) {
        return query(root, left, right);
    }
}




Version #2 Segment Tree
20.15 %
这个写法是对的!
每次在query的时候都要缩小query的范围,当query和tree的start end正好重合时候就直接返回

以前写的一个是query范围不变,那base case就变成了start == end, 这样每次都必须找到Leaf才能停止,时间复杂度就变成了O(n) 而不是logn


public class NumArray {
 
    class SegmentTree {
        int start, end;
        SegmentTree left, right;
        int sum;
        public SegmentTree(int start, int end, int sum) {
            this.start = start;
            this.end = end;
            this.sum = sum;
        }
    }
    private SegmentTree root;
    public NumArray(int[] nums) {
        this.root = init(nums, 0, nums.length - 1);
    }
 
    public SegmentTree init(int[] nums, int start, int end) {
        if (start > end) return null;
        if (start == end) return new SegmentTree(start, end, nums[start]);
        int mid = start + (end - start) / 2;
        SegmentTree left = init(nums, start, mid);
        SegmentTree right = init(nums, mid + 1, end);
        SegmentTree root = new SegmentTree(start, end, left.sum + right.sum);
        root.left = left;
        root.right = right;
        return root;  
    }
 
    public void update(int i, int val) {
        updateTree(i, root, val);
    }
    private void updateTree(int i, SegmentTree root, int val) {
        // out of range
        if (i < root.start || i > root.end) return;
        if (root.start == root.end) {
            root.sum = val;
            return;
        }
        //i <= mid
        if (i <= root.left.end) {
            updateTree(i, root.left, val);
        } else {
            updateTree(i, root.right, val);
        }
        root.sum = root.left.sum + root.right.sum;
    }
 
    public int sumRange(int i, int j) {
        return sumRangeTree(i, j, root);
    }
    private int sumRangeTree(int left, int right, SegmentTree root) {
        if (right < root.start || left > root.end) return 0;
        //in Range -> start, left, right, end
        if (root.start == left && root.end == right) return root.sum;
        int mid = root.start + (root.end - root.start) / 2;
        if (right <= mid) {
            return sumRangeTree(left, right, root.left);
        } else if (left >= mid + 1) {
            return sumRangeTree(left, right, root.right);
        } else {
            return sumRangeTree(left, mid, root.left) + sumRangeTree(mid + 1, right, root.right);
        }
    }
}


Version #1 Binary Indexed Tree
二刷
每次的update事实上分两步
第一步是update tree with diff
这里的treeIndex = i+1
第二步是update nums array

62.91 %
class NumArray {

    int[] sums;
    int[] nums;
    int n;
    public NumArray(int[] nums) {
        n = nums.length;
        sums = new int[n + 1]; // 1 indexed
        this.nums = nums;
        for (int i = 0; i < n; i++) {
            updateTree(i, nums[i]);
        }
    }
   
    public void update(int i, int val) {
        int diff = val - nums[i];
        updateTree(i, diff);
        nums[i] = val;
    }
   
    private void updateTree(int i, int val) {
        i++;
        while (i <= n) {
            sums[i] += val;
            i += (i & (-i));
        }
    }
   
    public int sumRange(int i, int j) {
        return prefix(j) - prefix(i - 1);
    }
   
    private int prefix(int i) {
        int sum = 0;
        i++;
        while (i > 0) {
            sum += sums[i];
            i -= (i & (-i));
        }
        return sum;
    }
}



一刷

这个tree有一个神奇的性质
i = i + (i & (-i)); 找到的是它右上最近parent的index
 i = i - (i & (-i));  找到的是它左上最近parent的index

注意number array的index是0 -> length - 1
而Binary Indexed Tree 的index 是 1 -> length
所以每次操作 ti = ni + 1

写了一个晕了的bug
传进来的nums array是initialize 用的array
而数据结构本身同样需要一个nums array来记录每一个点的值
因为BIT记录的是leftSum(the value of the node itself is included),所以需要一个nums array来记录node本身的value
在update的时候的diff也是value 和nums[ni] 之间的差
Space O(n)
Time construction O(nlogn)
         query O(logn)
         update O(logn)


50.87 %
public class NumArray {
    int[] nums;
    int[] leftSum;
    public NumArray(int[] nums) {
        if (nums == null) return;
        this.nums = new int[nums.length];
        this.leftSum = new int[nums.length + 1];
        //ni -> index of nums array
        //ti -> index of tree
        for (int ni = 0; ni < nums.length; ni++) {
            update(ni, nums[ni]);
            this.nums[ni] = nums[ni];
        }
    }

    public void update(int i, int val) {
        int diff = val - nums[i];
        nums[i] = val;
        // ni -> ti
        i += 1;
        while (0 < i && i < leftSum.length) {
            leftSum[i] += diff;
            i = i + (i & (-i));
        }
    }
    private int prefixSum(int i) {
        i += 1;
        int sum = 0;
        while (0 < i && i < leftSum.length) {
            sum += leftSum[i];
            i = i - (i & (-i));
        }
        return sum;
    }
    public int sumRange(int i, int j) {
   
        return prefixSum(j) - prefixSum(i - 1);
    }
}

No comments:

Post a Comment