Facebook Pixel

308. Range Sum Query 2D - Mutable ๐Ÿ”’

MediumDesignBinary Indexed TreeSegment TreeArrayMatrix
Leetcode Link

Problem Description

This problem asks you to implement a data structure that efficiently handles a 2D matrix with two main operations:

  1. Update Operation: Change the value of a specific cell in the matrix at position (row, col) to a new value val.

  2. Range Sum Query: Calculate the sum of all elements within a rectangular region defined by its upper-left corner (row1, col1) and lower-right corner (row2, col2).

You need to implement a NumMatrix class with three methods:

  • NumMatrix(matrix): Constructor that initializes the data structure with a given 2D integer matrix.
  • update(row, col, val): Updates the value at matrix[row][col] to val.
  • sumRegion(row1, col1, row2, col2): Returns the sum of all elements in the rectangle from (row1, col1) to (row2, col2) inclusive.

The challenge is to design this data structure so that both update and sum operations are performed efficiently, especially when there are many queries. A naive approach of updating the matrix directly and calculating sums by iterating through the rectangle would work but may be too slow for large matrices with frequent operations.

The solution uses a Binary Indexed Tree (also known as Fenwick Tree) for each row of the matrix. This allows for efficient updates in O(log n) time and prefix sum queries in O(log n) time per row. For a range sum query spanning multiple rows, the solution queries each row's Binary Indexed Tree and sums the results, achieving O(m * log n) time complexity where m is the number of rows in the query range and n is the number of columns.

Quick Interview Experience
Help others by sharing your interview experience
Have you seen this problem before?

Intuition

When faced with a problem requiring frequent updates and range sum queries on a 2D matrix, the first thought might be to simply store the matrix and compute sums by iterating through the required rectangle. However, this would take O(m * n) time for each sum query where m and n are the dimensions of the query rectangle.

The key insight is recognizing that this is essentially a range sum problem with updates, which is a classic use case for advanced data structures. For 1D arrays, we could use a Binary Indexed Tree (Fenwick Tree) or Segment Tree to achieve O(log n) time for both updates and prefix sum queries.

To extend this to 2D, we can think of the matrix as a collection of independent rows. Each row can be treated as a 1D array with its own Binary Indexed Tree. This decomposition works because:

  1. When we update a cell at (row, col), we only need to update the Binary Indexed Tree for that specific row.
  2. When we calculate a rectangular sum, we can break it down into row-by-row calculations. For each row in the range [row1, row2], we calculate the sum of elements from col1 to col2.

The Binary Indexed Tree is particularly elegant here because:

  • It uses the binary representation of indices to efficiently store partial sums
  • The lowbit(x) = x & -x operation extracts the least significant bit, which determines how far to jump in the tree structure
  • Updates propagate upward through the tree by adding lowbit(x) to the current index
  • Queries move downward by subtracting lowbit(x) from the current index

By maintaining one Binary Indexed Tree per row, we achieve:

  • Update: O(log n) time (updating one tree)
  • Sum query: O(m * log n) time where m is the number of rows in the query range

This approach strikes a good balance between space efficiency (O(m * n) space) and time efficiency for both operations, making it much faster than the naive approach when dealing with many queries.

Solution Approach

The solution implements a 2D range sum query with updates using Binary Indexed Trees (Fenwick Trees). Let's walk through each component:

Binary Indexed Tree Implementation

The BinaryIndexedTree class maintains a 1D Fenwick Tree:

  1. Initialization: Creates an array c of size n + 1 (1-indexed for easier implementation) initialized with zeros.

  2. lowbit function: x & -x extracts the rightmost set bit. This determines the range of responsibility for each node in the tree.

  3. update method: Adds delta to position x and propagates the change upward:

    while x <= self.n:
        self.c[x] += delta
        x += BinaryIndexedTree.lowbit(x)

    Each iteration moves to the next parent node that needs updating.

  4. query method: Computes the prefix sum from index 1 to x:

    while x > 0:
        s += self.c[x]
        x -= BinaryIndexedTree.lowbit(x)

    Each iteration adds the value at the current node and moves to the previous range.

NumMatrix Implementation

  1. Initialization:

    • Creates a list of Binary Indexed Trees, one for each row of the matrix
    • For each row, initializes a new tree and populates it with the row's values
    • Uses 1-based indexing by calling tree.update(j + 1, v) for each element
  2. Update operation:

    tree = self.trees[row]
    prev = tree.query(col + 1) - tree.query(col)
    tree.update(col + 1, val - prev)
    • Gets the tree for the specified row
    • Calculates the current value at (row, col) using the difference of two prefix sums
    • Updates the tree with the delta (val - prev) to change the cell value
  3. sumRegion operation:

    return sum(
        tree.query(col2 + 1) - tree.query(col1)
        for tree in self.trees[row1 : row2 + 1]
    )
    • Iterates through each row in the range [row1, row2]
    • For each row's tree, calculates the sum from col1 to col2 using the difference of prefix sums: query(col2 + 1) - query(col1)
    • Sums up all the row results to get the total rectangular sum

Time Complexity Analysis

  • Initialization: O(m * n * log n) where m is rows and n is columns
  • Update: O(log n) for updating one Binary Indexed Tree
  • sumRegion: O((row2 - row1 + 1) * log n) for querying multiple rows

This approach efficiently handles both updates and range queries by leveraging the logarithmic time operations of Binary Indexed Trees while maintaining reasonable space complexity.

Ready to land your dream job?

Unlock your dream job with a 5-minute evaluator for a personalized learning plan!

Start Evaluator

Example Walkthrough

Let's walk through a small example with a 3ร—3 matrix to illustrate how the solution works:

Initial Matrix:

[1, 2, 3]
[4, 5, 6]
[7, 8, 9]

Step 1: Initialization When we create NumMatrix(matrix), we build 3 Binary Indexed Trees (one per row):

  • Tree 0 for row [1, 2, 3]
  • Tree 1 for row [4, 5, 6]
  • Tree 2 for row [7, 8, 9]

For Tree 0 (row [1, 2, 3]), the internal BIT array c becomes:

  • c[1] = 1 (covers element at index 0)
  • c[2] = 3 (covers elements at indices 0-1, so 1+2=3)
  • c[3] = 3 (covers element at index 2)
  • c[4] = 9 (covers all elements, so 1+2+3=9)

Step 2: Query sumRegion(0, 1, 2, 2) This asks for the sum of the rectangle from (0,1) to (2,2):

[-, 2, 3]
[-, 5, 6]
[-, 8, 9]

For each row in range [0, 2]:

  • Row 0: tree0.query(3) - tree0.query(1) = 6 - 1 = 5 (elements 2+3)
  • Row 1: tree1.query(3) - tree1.query(1) = 15 - 4 = 11 (elements 5+6)
  • Row 2: tree2.query(3) - tree2.query(1) = 24 - 7 = 17 (elements 8+9)

Total sum = 5 + 11 + 17 = 33

Step 3: Update(1, 1, 10) Change matrix[1][1] from 5 to 10:

First, calculate current value at (1,1):

  • prev = tree1.query(2) - tree1.query(1) = 9 - 4 = 5

Then update with delta:

  • tree1.update(2, 10 - 5) = tree1.update(2, 5)

This propagates through the BIT:

  • c[2] += 5 (index 2 = 0010 in binary)
  • Next index: 2 + lowbit(2) = 2 + 2 = 4
  • c[4] += 5 (index 4 = 0100 in binary)

Step 4: Query sumRegion(0, 1, 2, 2) again After the update, the same rectangle sum:

  • Row 0: still 5 (unchanged)
  • Row 1: tree1.query(3) - tree1.query(1) = 20 - 4 = 16 (now 10+6)
  • Row 2: still 17 (unchanged)

New total sum = 5 + 16 + 17 = 38

The beauty of this approach is that the update only affected one Binary Indexed Tree (for row 1), and within that tree, only O(log n) nodes were modified. Similarly, each row's range sum query only requires O(log n) operations, making both operations highly efficient.

Solution Implementation

1from typing import List
2
3
4class BinaryIndexedTree:
5    """
6    Binary Indexed Tree (Fenwick Tree) for efficient range sum queries and updates.
7    Uses 1-indexed array internally for easier bit manipulation.
8    """
9  
10    def __init__(self, n: int) -> None:
11        """
12        Initialize a Binary Indexed Tree with n elements.
13      
14        Args:
15            n: Number of elements in the tree
16        """
17        self.n = n
18        self.tree = [0] * (n + 1)  # 1-indexed array for BIT
19  
20    @staticmethod
21    def _lowbit(x: int) -> int:
22        """
23        Get the lowest set bit of x.
24        This represents the range of responsibility for index x in the BIT.
25      
26        Args:
27            x: Input number
28          
29        Returns:
30            The value of the lowest set bit
31        """
32        return x & -x
33  
34    def update(self, index: int, delta: int) -> None:
35        """
36        Add delta to the value at the given index.
37        Updates all affected nodes in the tree.
38      
39        Args:
40            index: 1-based index to update
41            delta: Value to add to the element at index
42        """
43        while index <= self.n:
44            self.tree[index] += delta
45            index += self._lowbit(index)  # Move to next affected node
46  
47    def query(self, index: int) -> int:
48        """
49        Get the prefix sum from index 1 to the given index.
50      
51        Args:
52            index: 1-based index (inclusive) to calculate sum up to
53          
54        Returns:
55            Sum of elements from 1 to index
56        """
57        result = 0
58        while index > 0:
59            result += self.tree[index]
60            index -= self._lowbit(index)  # Move to parent node
61        return result
62
63
64class NumMatrix:
65    """
66    2D matrix with efficient range sum queries and single element updates.
67    Uses a Binary Indexed Tree for each row to handle operations.
68    """
69  
70    def __init__(self, matrix: List[List[int]]) -> None:
71        """
72        Initialize the NumMatrix with the given 2D matrix.
73        Creates a BIT for each row to enable efficient row-wise operations.
74      
75        Args:
76            matrix: 2D list of integers
77        """
78        self.row_trees = []
79        num_cols = len(matrix[0])
80      
81        # Create a BIT for each row and populate with initial values
82        for row in matrix:
83            bit = BinaryIndexedTree(num_cols)
84            for col_idx, value in enumerate(row):
85                # BIT uses 1-based indexing, so add 1 to column index
86                bit.update(col_idx + 1, value)
87            self.row_trees.append(bit)
88  
89    def update(self, row: int, col: int, val: int) -> None:
90        """
91        Update the value at matrix[row][col] to val.
92      
93        Args:
94            row: Row index (0-based)
95            col: Column index (0-based)
96            val: New value to set
97        """
98        bit = self.row_trees[row]
99        # Calculate current value at (row, col) using range query
100        current_val = bit.query(col + 1) - bit.query(col)
101        # Update with the difference to set new value
102        bit.update(col + 1, val - current_val)
103  
104    def sumRegion(self, row1: int, col1: int, row2: int, col2: int) -> int:
105        """
106        Calculate the sum of elements in the rectangle defined by
107        (row1, col1) as top-left and (row2, col2) as bottom-right corners.
108      
109        Args:
110            row1: Top row index (0-based, inclusive)
111            col1: Left column index (0-based, inclusive)
112            row2: Bottom row index (0-based, inclusive)
113            col2: Right column index (0-based, inclusive)
114          
115        Returns:
116            Sum of all elements in the specified rectangle
117        """
118        total_sum = 0
119        # Sum the range [col1, col2] for each row in [row1, row2]
120        for row_idx in range(row1, row2 + 1):
121            bit = self.row_trees[row_idx]
122            # Range sum from col1 to col2 using prefix sums
123            row_sum = bit.query(col2 + 1) - bit.query(col1)
124            total_sum += row_sum
125        return total_sum
126
127
128# Your NumMatrix object will be instantiated and called as such:
129# obj = NumMatrix(matrix)
130# obj.update(row, col, val)
131# param_2 = obj.sumRegion(row1, col1, row2, col2)
132
1/**
2 * Binary Indexed Tree (Fenwick Tree) implementation for efficient prefix sum queries and updates
3 */
4class BinaryIndexedTree {
5    private int size;
6    private int[] tree;
7
8    /**
9     * Initialize a Binary Indexed Tree with given size
10     * @param size the size of the array to be represented
11     */
12    public BinaryIndexedTree(int size) {
13        this.size = size;
14        // BIT uses 1-based indexing, so we need size + 1 elements
15        this.tree = new int[size + 1];
16    }
17
18    /**
19     * Update the value at position index by adding delta
20     * @param index the 1-based position to update
21     * @param delta the value to add at the position
22     */
23    public void update(int index, int delta) {
24        // Propagate the update up the tree
25        while (index <= size) {
26            tree[index] += delta;
27            // Move to the next node that this index affects
28            index += lowbit(index);
29        }
30    }
31
32    /**
33     * Query the prefix sum from index 1 to index (inclusive)
34     * @param index the 1-based ending position for the sum
35     * @return the prefix sum from 1 to index
36     */
37    public int query(int index) {
38        int sum = 0;
39        // Accumulate values while traversing down the tree
40        while (index > 0) {
41            sum += tree[index];
42            // Move to the parent node in the tree
43            index -= lowbit(index);
44        }
45        return sum;
46    }
47
48    /**
49     * Get the lowest set bit of a number
50     * This determines the range of responsibility for each node in the BIT
51     * @param x the input number
52     * @return the value of the lowest set bit
53     */
54    public static int lowbit(int x) {
55        return x & -x;
56    }
57}
58
59/**
60 * 2D Matrix with efficient range sum queries and single element updates
61 * Uses Binary Indexed Trees for each row
62 */
63class NumMatrix {
64    private BinaryIndexedTree[] rowTrees;
65
66    /**
67     * Initialize the matrix with given values
68     * Creates a Binary Indexed Tree for each row
69     * @param matrix the initial 2D matrix values
70     */
71    public NumMatrix(int[][] matrix) {
72        int numRows = matrix.length;
73        int numCols = matrix[0].length;
74      
75        // Create a BIT for each row
76        rowTrees = new BinaryIndexedTree[numRows];
77      
78        for (int row = 0; row < numRows; row++) {
79            BinaryIndexedTree currentRowTree = new BinaryIndexedTree(numCols);
80          
81            // Initialize the BIT with values from the matrix row
82            for (int col = 0; col < numCols; col++) {
83                // BIT uses 1-based indexing, so we use col + 1
84                currentRowTree.update(col + 1, matrix[row][col]);
85            }
86          
87            rowTrees[row] = currentRowTree;
88        }
89    }
90
91    /**
92     * Update a single element in the matrix
93     * @param row the row index (0-based)
94     * @param col the column index (0-based)
95     * @param val the new value to set
96     */
97    public void update(int row, int col, int val) {
98        BinaryIndexedTree currentRowTree = rowTrees[row];
99      
100        // Calculate the current value at matrix[row][col]
101        // This is done by querying the range sum from col to col (single element)
102        int currentValue = currentRowTree.query(col + 1) - currentRowTree.query(col);
103      
104        // Update with the difference between new and old value
105        currentRowTree.update(col + 1, val - currentValue);
106    }
107
108    /**
109     * Calculate the sum of elements in the rectangle defined by the corners
110     * @param row1 top row index (0-based, inclusive)
111     * @param col1 left column index (0-based, inclusive)
112     * @param row2 bottom row index (0-based, inclusive)
113     * @param col2 right column index (0-based, inclusive)
114     * @return the sum of all elements in the specified rectangle
115     */
116    public int sumRegion(int row1, int col1, int row2, int col2) {
117        int totalSum = 0;
118      
119        // Sum the range for each row in the rectangle
120        for (int row = row1; row <= row2; row++) {
121            BinaryIndexedTree currentRowTree = rowTrees[row];
122          
123            // Get sum from col1 to col2 (inclusive) using prefix sums
124            // Sum[col1, col2] = PrefixSum[col2] - PrefixSum[col1 - 1]
125            totalSum += currentRowTree.query(col2 + 1) - currentRowTree.query(col1);
126        }
127      
128        return totalSum;
129    }
130}
131
132/**
133 * Your NumMatrix object will be instantiated and called as such:
134 * NumMatrix obj = new NumMatrix(matrix);
135 * obj.update(row,col,val);
136 * int param_2 = obj.sumRegion(row1,col1,row2,col2);
137 */
138
1class BinaryIndexedTree {
2public:
3    int size;                    // Size of the array (1-indexed)
4    vector<int> tree;            // Fenwick tree array
5  
6    // Constructor: Initialize BIT with given size
7    BinaryIndexedTree(int n) : size(n), tree(n + 1) {}
8  
9    // Update the value at index by adding delta
10    // Time complexity: O(log n)
11    void update(int index, int delta) {
12        while (index <= size) {
13            tree[index] += delta;
14            index += lowbit(index);  // Move to next node that needs update
15        }
16    }
17  
18    // Query prefix sum from index 1 to index
19    // Time complexity: O(log n)
20    int query(int index) {
21        int sum = 0;
22        while (index > 0) {
23            sum += tree[index];
24            index -= lowbit(index);  // Move to parent node
25        }
26        return sum;
27    }
28  
29    // Get the lowest set bit of x
30    // Example: lowbit(6) = 2 (binary: 110 -> 010)
31    int lowbit(int x) {
32        return x & -x;
33    }
34};
35
36class NumMatrix {
37public:
38    vector<BinaryIndexedTree*> rowTrees;  // One BIT for each row
39  
40    // Constructor: Build BIT for each row of the matrix
41    // Time complexity: O(m * n * log n)
42    NumMatrix(vector<vector<int>>& matrix) {
43        int numRows = matrix.size();
44        int numCols = matrix[0].size();
45        rowTrees.resize(numRows);
46      
47        // Create a BIT for each row
48        for (int row = 0; row < numRows; ++row) {
49            BinaryIndexedTree* tree = new BinaryIndexedTree(numCols);
50            // Initialize each BIT with the row's values
51            for (int col = 0; col < numCols; ++col) {
52                tree->update(col + 1, matrix[row][col]);  // BIT uses 1-indexed
53            }
54            rowTrees[row] = tree;
55        }
56    }
57  
58    // Update matrix[row][col] to val
59    // Time complexity: O(log n)
60    void update(int row, int col, int val) {
61        BinaryIndexedTree* tree = rowTrees[row];
62        // Calculate current value at (row, col)
63        int currentVal = tree->query(col + 1) - tree->query(col);
64        // Update with the difference
65        tree->update(col + 1, val - currentVal);
66    }
67  
68    // Calculate sum of rectangle from (row1, col1) to (row2, col2)
69    // Time complexity: O(m * log n) where m is number of rows in range
70    int sumRegion(int row1, int col1, int row2, int col2) {
71        int totalSum = 0;
72        // Sum up each row's contribution
73        for (int row = row1; row <= row2; ++row) {
74            BinaryIndexedTree* tree = rowTrees[row];
75            // Range sum = prefixSum(col2) - prefixSum(col1-1)
76            totalSum += tree->query(col2 + 1) - tree->query(col1);
77        }
78        return totalSum;
79    }
80};
81
82/**
83 * Your NumMatrix object will be instantiated and called as such:
84 * NumMatrix* obj = new NumMatrix(matrix);
85 * obj->update(row,col,val);
86 * int param_2 = obj->sumRegion(row1,col1,row2,col2);
87 */
88
1// Binary Indexed Tree (Fenwick Tree) implementation
2let size: number;                    // Size of the array (1-indexed)
3let tree: number[];                  // Fenwick tree array
4
5// Initialize BIT with given size
6function initBIT(n: number): void {
7    size = n;
8    tree = new Array(n + 1).fill(0);
9}
10
11// Get the lowest set bit of x
12// Example: lowbit(6) = 2 (binary: 110 -> 010)
13function lowbit(x: number): number {
14    return x & -x;
15}
16
17// Update the value at index by adding delta
18// Time complexity: O(log n)
19function updateBIT(index: number, delta: number): void {
20    while (index <= size) {
21        tree[index] += delta;
22        index += lowbit(index);  // Move to next node that needs update
23    }
24}
25
26// Query prefix sum from index 1 to index
27// Time complexity: O(log n)
28function queryBIT(index: number): number {
29    let sum = 0;
30    while (index > 0) {
31        sum += tree[index];
32        index -= lowbit(index);  // Move to parent node
33    }
34    return sum;
35}
36
37// NumMatrix implementation using Binary Indexed Trees
38let rowTrees: { size: number; tree: number[] }[];  // One BIT for each row
39
40// Constructor: Build BIT for each row of the matrix
41// Time complexity: O(m * n * log n)
42function NumMatrix(matrix: number[][]): void {
43    const numRows = matrix.length;
44    const numCols = matrix[0].length;
45    rowTrees = [];
46  
47    // Create a BIT for each row
48    for (let row = 0; row < numRows; row++) {
49        // Create a new BIT for this row
50        const rowTree = {
51            size: numCols,
52            tree: new Array(numCols + 1).fill(0)
53        };
54      
55        // Initialize the BIT with the row's values
56        for (let col = 0; col < numCols; col++) {
57            // Update BIT at position col + 1 (BIT uses 1-indexed)
58            let idx = col + 1;
59            while (idx <= numCols) {
60                rowTree.tree[idx] += matrix[row][col];
61                idx += lowbit(idx);
62            }
63        }
64      
65        rowTrees.push(rowTree);
66    }
67}
68
69// Update matrix[row][col] to val
70// Time complexity: O(log n)
71function update(row: number, col: number, val: number): void {
72    const rowTree = rowTrees[row];
73  
74    // Calculate current value at (row, col) using range query
75    // Current value = prefixSum(col + 1) - prefixSum(col)
76    let currentVal = 0;
77    let idx = col + 1;
78    while (idx > 0) {
79        currentVal += rowTree.tree[idx];
80        idx -= lowbit(idx);
81    }
82    idx = col;
83    while (idx > 0) {
84        currentVal -= rowTree.tree[idx];
85        idx -= lowbit(idx);
86    }
87  
88    // Update with the difference between new value and current value
89    const delta = val - currentVal;
90    idx = col + 1;
91    while (idx <= rowTree.size) {
92        rowTree.tree[idx] += delta;
93        idx += lowbit(idx);
94    }
95}
96
97// Calculate sum of rectangle from (row1, col1) to (row2, col2)
98// Time complexity: O(m * log n) where m is number of rows in range
99function sumRegion(row1: number, col1: number, row2: number, col2: number): number {
100    let totalSum = 0;
101  
102    // Sum up each row's contribution
103    for (let row = row1; row <= row2; row++) {
104        const rowTree = rowTrees[row];
105      
106        // Calculate range sum for this row
107        // Range sum = prefixSum(col2 + 1) - prefixSum(col1)
108        let rowSum = 0;
109      
110        // Add prefixSum(col2 + 1)
111        let idx = col2 + 1;
112        while (idx > 0) {
113            rowSum += rowTree.tree[idx];
114            idx -= lowbit(idx);
115        }
116      
117        // Subtract prefixSum(col1)
118        idx = col1;
119        while (idx > 0) {
120            rowSum -= rowTree.tree[idx];
121            idx -= lowbit(idx);
122        }
123      
124        totalSum += rowSum;
125    }
126  
127    return totalSum;
128}
129
130/**
131 * Your NumMatrix object will be instantiated and called as such:
132 * NumMatrix(matrix);
133 * update(row, col, val);
134 * let sum = sumRegion(row1, col1, row2, col2);
135 */
136

Time and Space Complexity

Time Complexity

Initialization (__init__):

  • For each row in the matrix (m rows), we create a BinaryIndexedTree and update it with n values
  • Each update operation in BinaryIndexedTree takes O(log n) time
  • Total initialization time: O(m * n * log n)

Update operation (update):

  • First, we calculate the previous value at position (row, col) using two query operations: O(log n)
  • Then, we update the BinaryIndexedTree with the difference: O(log n)
  • Total update time: O(log n)

SumRegion operation (sumRegion):

  • We iterate through (row2 - row1 + 1) rows
  • For each row, we perform two query operations on the BinaryIndexedTree: O(log n)
  • Total sumRegion time: O((row2 - row1 + 1) * log n) = O(m * log n) in the worst case when we query all rows

Space Complexity

Space usage:

  • We store m BinaryIndexedTree objects, one for each row
  • Each BinaryIndexedTree uses an array of size (n + 1)
  • Total space complexity: O(m * n)

The additional space used by the algorithm (excluding the input matrix) is O(m * n) for storing the Binary Indexed Trees.

Common Pitfalls

1. Index Confusion Between 0-based and 1-based Systems

The most common pitfall in this implementation is mixing up 0-based indexing (used by the matrix) and 1-based indexing (used internally by the Binary Indexed Tree).

The Problem:

  • The matrix uses 0-based indexing: matrix[0][0] is the first element
  • The BIT uses 1-based indexing internally for easier bit manipulation
  • Developers often forget to add 1 when converting from matrix indices to BIT indices

Example of the Bug:

# WRONG - forgetting to convert to 1-based indexing
def update(self, row: int, col: int, val: int) -> None:
    bit = self.row_trees[row]
    current_val = bit.query(col) - bit.query(col - 1)  # Bug: using 0-based indices
    bit.update(col, val - current_val)  # Bug: using 0-based index

The Solution: Always add 1 when calling BIT methods with matrix indices:

# CORRECT - properly converting to 1-based indexing
def update(self, row: int, col: int, val: int) -> None:
    bit = self.row_trees[row]
    current_val = bit.query(col + 1) - bit.query(col)  # Add 1 for 1-based indexing
    bit.update(col + 1, val - current_val)  # Add 1 here too

2. Incorrect Range Sum Calculation

The Problem: When calculating the sum for a range [col1, col2], developers might incorrectly use query(col2) - query(col1), which would exclude the element at col2.

Example of the Bug:

# WRONG - excludes the element at col2
row_sum = bit.query(col2) - bit.query(col1)

The Solution: Remember that query(x) returns the sum from index 1 to x (inclusive), so for range [col1, col2]:

# CORRECT - includes both col1 and col2
row_sum = bit.query(col2 + 1) - bit.query(col1)

This gives us sum[1...col2+1] - sum[1...col1] = sum[col1+1...col2+1] in 1-based terms, which corresponds to sum[col1...col2] in 0-based terms.

3. Attempting to Update by Setting Instead of Adding Delta

The Problem: The BIT's update method adds a delta to the existing value, not sets a new value directly. Forgetting this leads to incorrect updates.

Example of the Bug:

# WRONG - BIT.update adds delta, doesn't set value
def update(self, row: int, col: int, val: int) -> None:
    self.row_trees[row].update(col + 1, val)  # This ADDS val, doesn't SET to val

The Solution: Calculate the difference between new and old values:

# CORRECT - calculate delta first
def update(self, row: int, col: int, val: int) -> None:
    bit = self.row_trees[row]
    current_val = bit.query(col + 1) - bit.query(col)
    delta = val - current_val  # Calculate the change needed
    bit.update(col + 1, delta)  # Add the delta to achieve the desired value

4. Memory and Performance Considerations

The Problem: Creating unnecessary intermediate data structures or using inefficient list operations.

Example of the Bug:

# INEFFICIENT - creates unnecessary list in memory
def sumRegion(self, row1: int, col1: int, row2: int, col2: int) -> int:
    results = []
    for row_idx in range(row1, row2 + 1):
        bit = self.row_trees[row_idx]
        results.append(bit.query(col2 + 1) - bit.query(col1))
    return sum(results)  # Creates intermediate list unnecessarily

The Solution: Use generator expressions or accumulate directly:

# EFFICIENT - uses generator expression
def sumRegion(self, row1: int, col1: int, row2: int, col2: int) -> int:
    return sum(
        self.row_trees[row].query(col2 + 1) - self.row_trees[row].query(col1)
        for row in range(row1, row2 + 1)
    )
Discover Your Strengths and Weaknesses: Take Our 5-Minute Quiz to Tailor Your Study Plan:

What's the output of running the following function using input 56?

1KEYBOARD = {
2    '2': 'abc',
3    '3': 'def',
4    '4': 'ghi',
5    '5': 'jkl',
6    '6': 'mno',
7    '7': 'pqrs',
8    '8': 'tuv',
9    '9': 'wxyz',
10}
11
12def letter_combinations_of_phone_number(digits):
13    def dfs(path, res):
14        if len(path) == len(digits):
15            res.append(''.join(path))
16            return
17
18        next_number = digits[len(path)]
19        for letter in KEYBOARD[next_number]:
20            path.append(letter)
21            dfs(path, res)
22            path.pop()
23
24    res = []
25    dfs([], res)
26    return res
27
1private static final Map<Character, char[]> KEYBOARD = Map.of(
2    '2', "abc".toCharArray(),
3    '3', "def".toCharArray(),
4    '4', "ghi".toCharArray(),
5    '5', "jkl".toCharArray(),
6    '6', "mno".toCharArray(),
7    '7', "pqrs".toCharArray(),
8    '8', "tuv".toCharArray(),
9    '9', "wxyz".toCharArray()
10);
11
12public static List<String> letterCombinationsOfPhoneNumber(String digits) {
13    List<String> res = new ArrayList<>();
14    dfs(new StringBuilder(), res, digits.toCharArray());
15    return res;
16}
17
18private static void dfs(StringBuilder path, List<String> res, char[] digits) {
19    if (path.length() == digits.length) {
20        res.add(path.toString());
21        return;
22    }
23    char next_digit = digits[path.length()];
24    for (char letter : KEYBOARD.get(next_digit)) {
25        path.append(letter);
26        dfs(path, res, digits);
27        path.deleteCharAt(path.length() - 1);
28    }
29}
30
1const KEYBOARD = {
2    '2': 'abc',
3    '3': 'def',
4    '4': 'ghi',
5    '5': 'jkl',
6    '6': 'mno',
7    '7': 'pqrs',
8    '8': 'tuv',
9    '9': 'wxyz',
10}
11
12function letter_combinations_of_phone_number(digits) {
13    let res = [];
14    dfs(digits, [], res);
15    return res;
16}
17
18function dfs(digits, path, res) {
19    if (path.length === digits.length) {
20        res.push(path.join(''));
21        return;
22    }
23    let next_number = digits.charAt(path.length);
24    for (let letter of KEYBOARD[next_number]) {
25        path.push(letter);
26        dfs(digits, path, res);
27        path.pop();
28    }
29}
30

Recommended Readings

Want a Structured Path to Master System Design Too? Donโ€™t Miss This!

Load More