1. 程式人生 > 實用技巧 >307. 區域和檢索 - 陣列可修改(樹狀陣列,線段樹)

307. 區域和檢索 - 陣列可修改(樹狀陣列,線段樹)

方法一:樹狀陣列

class NumArray {
    int[] nums;
    int[] bitArr;
    int n;
    public NumArray(int[] nums) {
        n = nums.length;
        this.nums = nums;
        bitArr = new int[n+1];
        for(int i = 1; i <= n; i++) bitArr[i] = nums[i-1];
        for(int i = 1; i <= n; i++) {
            
int j = i + lowbit(i); if(j <= n) bitArr[j] += bitArr[i]; } } public int lowbit(int x) {return x&(-x);} public void update(int i, int val) { int diff = val - nums[i]; nums[i] = val; i++; for(int j = i; j <= n; j += lowbit(j)) { bitArr[j]
+= diff; } } public int prefixSum(int x) { int res = 0; x++; for(int i = x; i > 0; i -= lowbit(i)) { res += bitArr[i]; } return res; } public int sumRange(int i, int j) { return prefixSum(j) - prefixSum(i-1); } }

方法二:線段樹

class NumArray {
    private Node root;
    int[] nums;
    public NumArray(int[] nums) {
        if(nums != null && nums.length > 0) {
            this.nums = nums;
            root = new Node(0,nums.length-1); // 根節點包含全部區間
            buildTree(root); // 建樹
        }
    }
    public int buildTree(Node root) {
        int l = root.l, r = root.r;
        if(l == r) { // 遞迴終止條件為到達葉節點
            root.sum = nums[l];
            return root.sum;
        }
        int mid = (l + r) / 2;
        root.left = new Node(l,mid);
        root.right = new Node(mid+1,r);
        int lval = buildTree(root.left);
        int rval = buildTree(root.right); // 左右遞迴建樹, 返回值為區間和
        root.sum = lval + rval;
        return root.sum;
    }


    public void update(int i, int val) {
        set(root,i,val);
    }
    public void set(Node root, int i, int val) { //遞迴更新單值
        if(root.l == root.r && root.l == i) {
            root.sum = val; // 終止條件為找到單值
            return;
        }
        int l = root.l, r = root.r;
        int mid = (l + r) / 2;
        if(i <= mid) {
            set(root.left,i,val);
        } else {
            set(root.right,i,val);
        }                   // 每次遞迴結束回到上一個遞迴棧都要同時跟新父節點的區間和
        root.sum = root.left.sum + root.right.sum; 
        return;
    }
    
    

    public int sumRange(int i, int j) {
        return query(i,j,root);
    }

    public int query(int i, int j, Node root) { // 遞迴查詢某一段區間和      
        if(root.l == i && root.r == j) {
            return root.sum;// 遞迴終止條件為找到這一段區間,返回區間和
        }
        int mid = (root.l + root.r) / 2;
        if(i > mid) {  // [l,mid]為左子樹 i > mid 向右子樹遞迴
            return query(i,j,root.right);
        }
        if(j < mid + 1) { // [mid+1,r]為右子樹 j < mid+1 向左子樹遞迴
            return query(i,j,root.left);
        }
           //這種情況為i與j在mid兩端所以要取[i,mid]和[mid+1,j]的和
        return query(i,mid,root.left) + query(mid+1,j,root.right);

    }
}
class Node { //定義區間樹的節點
    int l, r; // 區間的左值和右值
    int sum; // 區間總和
    Node left,right; // 左右節點
    public Node(int l, int r) {
        this.l = l;
        this.r = r;
    }
}