树状数组Binary Indexed Tree及相关LeetCode题目

关于我的 Leetcode 题目解答,代码前往 Github:https://github.com/chenxiangcyr/leetcode-answers


问题提出

有一个数组 nums[0 . . . n-1],我们希望能提供如下两个功能:

  • 功能1:求出前 i 个元素的和 sum
  • 功能2:改变某个元素的值,即nums[i] = x

可以看出,功能1的时间复杂度为O(n),功能2的时间复杂度为O(1)
当然,为了改进功能1的时间复杂度,我们可以新建一个数组int[] sum,用于记录前 i 个元素的和,即 sum[i] = nums[0] + nums[1] + ... + nums[i]。但是此时,功能2的时间复杂度变为了O(n),因为需要去维护数组 sum

LeetCode题目: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:
The array is only modifiable by the update function.
You may assume the number of calls to update and sumRange function is distributed evenly.

如何优化

是否可以在 O(log n) 的时间内完成上述的两个功能?
一种方法是使用Segment Tree 线段树,具体参见Segment Tree 线段树 原理及实现。

还有一种方法是使用树状数组Binary Indexed Tree。对比Segment Tree 线段树,它的优势是使用空间更小,更容易实现。

树状数组Binary Indexed Tree

使用数组存储,假设为 BITree[],每一个元素存储的是原数组 arr[]某些元素的和

一个特殊的位运算:
获取数字 A 的最低有效位:A & -A 或者 A & ~(A - 1)
例如数字 6(110)的最低位对应 2(10)

清除数字 A 的最低有效位:A = A - (A & -A) 或者 A = A - (A & ~(A - 1))
例如数字 6(110)的最低位对应 2(10),清除后得到 4(100)

更多请参见 Bit Manipulation 位运算常见套路及相应LeetCode算法题

基本思想

每个整数都能表示为一些2的幂次方的和,比如13,其二进制表示为1101,所以它能表示为:
13 = 1 + 4 + 8 = 2^0 + 2^2 + 2^3 = 1 + 100 + 1000
类似的,累积和可表示为其子集合之和。

i记为BITree的索引(从1开始),r记为i的二进制表示中最右边的1后面0的个数, 则BITree[i]记为数组中, 索引从(i-2^r +1)i的所有数的和,包含nums[i-2^r +1]nums[i]
例如:
i = 12 (1100), r = 2, i-2^r +1 = 9, BITree[12] = 第9个元素+...+第12个元素 = nums[8]+...+nums[11]
i = 13 (1101), r = 0, i-2^r +1 = 13, BITree[13] = 第13个元素 = nums[12]
如图所示:

树状数组Binary Indexed Tree及相关LeetCode题目_第1张图片
image

可以看出,如果要求前13个元素的和:
sum(13) = BITree[13]+BITree[12]+BITree[8] = BITree[1101]+BITree[1100]+BITree[1000]

可以看出,如果要更新第3个元素的值,会影响到三个BITree元素:

  • BITree[3],即BITree[11]
  • BITree[4],即BITree[11 + 1 = 100]
  • BITree[8],即BITree[100 + 100 = 1000]
树状数组Binary Indexed Tree及相关LeetCode题目_第2张图片
image

求出前 i 个元素的和

    // 求出前 `i` 个元素的和
    public int sum(int i) {
        int sum = 0;
        
        i++;
        while(i > 0) {
            sum = sum + BITree[i];
            i = i - (i & -i);
        }
        
        return sum;
    }

求出区间元素的和

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

改变某个元素的值,即nums[i] = x

    // 改变某个元素的值
    public void update(int i, int val) {
        int diff = val - nums[i];
        
        nums[i] = val;
        
        updateBIT(i, diff);
    }
    
    // 更新树状数组Binary Indexed Tree
    public void updateBIT(int i, int val) {
        i++;
        while(i <= n) {
            BITree[i] = BITree[i] + val;
            i = i + (i & -i);
        }
    }

Binary Indexed Tree的构造

    // 树状数组Binary Indexed Tree
    int[] BITree;
    
    int[] nums;
    
    int n;
    
    public NumArray(int[] nums) {
        this.nums = nums;
        this.n = nums.length;
        
        // 初始化树状数组Binary Indexed Tree
        // 索引都从1开始
        this.BITree = new int[n + 1];
        Arrays.fill(BITree, 0);
        
        for(int i = 0; i < n; i++) {
            updateBIT(i, nums[i]);
        }
    }

二维树状数组Binary Indexed Tree

LeetCode题目:308. Range Sum Query 2D - Mutable
Given a 2D matrix matrix, find the sum of the elements inside the rectangle defined by its upper left corner (row1, col1) and lower right corner (row2, col2).

树状数组Binary Indexed Tree及相关LeetCode题目_第3张图片
Range Sum Query 2D

The above rectangle (with the red border) is defined by (row1, col1) = (2, 1) and (row2, col2) = (4, 3), which contains sum = 8.
Example:

Given matrix = [
[3, 0, 1, 4, 2],
[5, 6, 3, 2, 1],
[1, 2, 0, 1, 5],
[4, 1, 0, 1, 7],
[1, 0, 3, 0, 5]
]
sumRegion(2, 1, 4, 3) -> 8
update(3, 2, 2)
sumRegion(2, 1, 4, 3) -> 10

Note:

  1. The matrix is only modifiable by the update function.
  2. You may assume the number of calls to update and sumRegion function is distributed evenly.
  3. You may assume that row1 ≤ row2 and col1 ≤ col2.
class NumMatrix {
    int[][] BITree;
    int[][] nums;
    int m;
    int n;
    
    public NumMatrix(int[][] matrix) {
        if (matrix == null || matrix.length == 0 || matrix[0].length == 0) return;
        
        m = matrix.length;
        n = matrix[0].length;
        
        BITree = new int[m + 1][n + 1];
        nums = new int[m][n];
        
        for (int i = 0; i < m; i++) {
            for (int j = 0; j < n; j++) {
                update(i, j, matrix[i][j]);
            }
        }
    }
    
    public void update(int row, int col, int val) {
        if (m == 0 || n == 0) return;
        
        int delta = val - nums[row][col];
        
        nums[row][col] = val;
        
        int i = row + 1;
        while(i <= m) {
            
            int j = col + 1;
            while(j <= n) {
                BITree[i][j] += delta;
                
                j += j & (-j);
            }
            
            i += i & (-i);
        }
    }
    
    public int sumRegion(int row1, int col1, int row2, int col2) {
        if (m == 0 || n == 0) return 0;
        
        return sum(row2, col2) + sum(row1 - 1, col1 - 1) - sum(row1 - 1, col2) - sum(row2, col1 - 1);
    }
    
    public int sum(int row, int col) {
        int sum = 0;
        
        int i = row + 1;
        while(i > 0) {
            
            int j = col + 1;
            while(j > 0) {
                sum = sum + BITree[i][j];
                
                j -= j & (-j);
            }
            
            i -= i & (-i);
        }
        
        return sum;
    }
}

/**
 * Your NumMatrix object will be instantiated and called as such:
 * NumMatrix obj = new NumMatrix(matrix);
 * obj.update(row,col,val);
 * int param_2 = obj.sumRegion(row1,col1,row2,col2);
 */

树状数组Binary Indexed Tree的扩展

树状数组Binary Indexed Tree不光可以用于求和,还可以求频率。
LeetCode题目:493. Reverse Pairs
Given an array nums, we call (i, j) an important reverse pair if i < j and nums[i] > 2*nums[j].
You need to return the number of important reverse pairs in the given array.

Example1:
Input: [1,3,2,3,1]
Output: 2

Example2:
Input: [2,4,3,5,1]
Output: 3

class Solution {
    private int search(int[] BITree, int i) {
        int sum = 0;

        while (i < BITree.length) {
            sum = sum + BITree[i];
            i = i + (i & -i);
        }

        return sum;
    }

    private void insert(int[] BITree, int i) {
        while (i > 0) {
            BITree[i] += 1;
            i = i - (i & -i);
        }
    }
    
    public int reversePairs(int[] nums) {
        int res = 0;
        int[] copy = Arrays.copyOf(nums, nums.length);
        int[] BITree = new int[copy.length + 1];

        Arrays.sort(copy);

        for (int num : nums) {
            // 先查找在之前的数字中,有多少个val大于等于nums[i] * 2l + 1的数字
            res += search(BITree, index(copy, 2L * num + 1));
            
            // 再插入该数字
            insert(BITree, index(copy, num));
        }

        return res;
    }

    // 在一个有序数组中搜索
    // For each element, the "index" function will return its index in the BIT.
    // Unlike the BST-based solution, this is guaranteed to run at O(nlogn)
    private int index(int[] arr, long val) {
        int l = 0, r = arr.length - 1, m = 0;

        while (l <= r) {
            m = l + ((r - l) >> 1);

            if (arr[m] >= val) {
                r = m - 1;
            } else {
                l = m + 1;
            }
        }

        return l + 1;
    }
}

引用:
树状数组(Binary Indexed Trees)
Binary Indexed Tree or Fenwick Tree

你可能感兴趣的:(树状数组Binary Indexed Tree及相关LeetCode题目)