Leetcode 1476. Subrectangle Queries

Problem:

The problem requires implementing a class SubrectangleQueries, which is initialized with a 2D integer matrix (a rectangle). This class should support two methods:

  1. updateSubrectangle(row1, col1, row2, col2, newValue): this method should replace all values in the subrectangle within the main rectangle with newValue. The subrectangle is defined by top-left corner (row1, col1) and bottom-right corner (row2, col2) .

  2. getValue(row, col): this method should return the current value at coordinate (row, col) in the rectangle.

The class and the methods should be implemented in such a way that they support up to 500 operations.

Example:

Let's illustrate the problem using an example: Assume that we have the following input:

1
2
3SubrectangleQueries rectangleQuery = new SubrectangleQueries([[1,2,3],[4,5,6],[7,8,9]]);
4// The initial rectangle (3x3) looks like:
5// 1 2 3
6// 4 5 6
7// 7 8 9
8rectangeQuery.getValue(0, 2); // return 3
9rectangleQuery.updateSubrectangle(1, 1, 2, 2, 5);
10// After this update the rectangle looks like:
11// 1 2 3
12// 4 5 6
13// 7 5 5
14
15rectangleQuery.getValue(1, 2); // return 6
16rectangleQuery.getValue(2, 2); // return 5

Solution:

The solution is to maintain a list of updates in the order they are performed. Each update is a list containing the top-left and bottom-right coordinates of the subrectangle to be updated, along with the newValue.

When we want to get a value in the rectangle with getValue(row, col), we iterate the update list backwards (most recent update first) and check if the coordinate (row, col) is inside the subrectangle of the update. If it is, then the value of (row, col) is the newValue of that update. If (row, col) is not in any updated subrectangle, we simply return the original value in the rectangle.

The main advantage of this solution is that we don't need to actually perform the update operation on the original rectangle, which could potentially be large. This makes the updateSubrectangle() operation extremely fast (constant time). The getValue() operation is also fast for small number of updates.

Python Solution:

Here is the pythonic version of the class SubrectangleQueries:

1
2python
3class SubrectangleQueries:
4    def __init__(self, rectangle):
5        self.rectangle = rectangle
6        self.updates = []  # Each update is a list: [row1, col1, row2, col2, newValue]
7
8    def updateSubrectangle(self, row1, col1, row2, col2, newValue):
9        self.updates.append([row1, col1, row2, col2, newValue])
10
11    def getValue(self, row, col):
12        for update in reversed(self.updates):
13            r1, c1, r2, c2, val = update
14            if r1 <= row <= r2 and c1 <= col <= c2:
15                return val
16        return self.rectangle[row][col]

Let's also implement this class in other programming languages.

Java Solution:

1
2java
3public class SubrectangleQueries {
4    private int[][] rectangle;
5    private List<int[]> updates;
6
7    public SubrectangleQueries(int[][] rectangle) {
8        this.rectangle = rectangle;
9        this.updates = new ArrayList<int[]>(); 
10    }
11
12    public void updateSubrectangle(int row1, int col1, int row2, int col2, int newValue) {
13        this.updates.add(new int[]{row1, col1, row2, col2, newValue});
14    }
15
16    public int getValue(int row, int col) {
17        for (int i = updates.size() - 1; i >= 0; --i) {
18            int[] update = updates.get(i);
19            int r1 = update[0], c1 = update[1], r2 = update[2], c2 = update[3], val = update[4];
20            if (r1 <= row && row <= r2 && c1 <= col && col <= c2) return val;
21        }
22        return rectangle[row][col];
23    }
24}

Javascript Solution:

1
2javascript
3class SubrectangleQueries {
4    constructor(rectangle) {
5        this.rectangle = rectangle;
6        this.updates = [];
7    }
8
9    updateSubrectangle(row1, col1, row2, col2, newValue) {
10        this.updates.push([row1, col1, row2, col2, newValue]);
11    }
12
13    getValue(row, col) {
14        for (let i = this.updates.length - 1; i >= 0; --i) {
15            let [r1, c1, r2, c2, val] = this.updates[i];
16            if (r1 <= row && row <= r2 && c1 <= col && col <= c2) return val;
17        }
18        return this.rectangle[row][col];
19    }
20}

C++ Solution:

1
2c++
3class SubrectangleQueries {
4public:
5    SubrectangleQueries(vector<vector<int>>& rectangle) {
6        this->rectangle = rectangle;
7    }
8
9    void updateSubrectangle(int row1, int col1, int row2, int col2, int newValue) {
10        updates.push_back({ row1, col1, row2, col2, newValue });
11    }
12
13    int getValue(int row, int col) {
14        for (int i = updates.size() - 1; i >= 0; --i) {
15            auto &update = updates[i];
16            if (update[0] <= row && row <= update[2] && update[1] <= col && col <= update[3])
17                return update[4];
18        }
19        return rectangle[row][col];
20    }
21
22private:
23    vector<vector<int>> rectangle;
24    vector<vector<int>> updates;
25};

C# Solution:

1
2csharp
3public class SubrectangleQueries {
4    private int[][] rectangle;
5    private List<int[]> updates = new List<int[]>();
6
7    public SubrectangleQueries(int[][] rectangle) {
8        this.rectangle = rectangle;
9    }
10
11    public void UpdateSubrectangle(int row1, int col1, int row2, int col2, int newValue) {
12        updates.Add(new int[]{ row1, col1, row2, col2, newValue });
13    }
14
15    public int GetValue(int row, int col) {
16        for (int i = updates.Count - 1; i >= 0; --i) {
17            var update = updates[i];
18            if (update[0] <= row && row <= update[2] && update[1] <= col && col <= update[3])
19                return update[4];
20        }
21        return rectangle[row][col];
22    }
23}

These codes provide the required functionality to answer the queries. They first take the rectangle as input. Then for every updateSubrectangle(row1, col1, row2, col2, newValue) query, they store the updates in a list. And finally for every getValue(row, col) query, they return the overlapping rectangle's new value in the reverse order of updates or the original rectangle value if no updates performed on the cell.# Conclusion:

The problems involving updates and queries on arrays or matrices are common in interviews and Competitive Programming. The above-mentioned problem is a representative of those types of problems. Hence, understanding this problem and its solutions can be really helpful in solving many similar problems.

The way these solutions utilize a data structure (list in this case) to store updates and then use this information later to get values is a clever way of optimizing the solution. It handles queries in constant time, making it efficient especially when dealing with matrix or array of large size.

The above solutions are implemented in five popular programming languages: Python, Java, JavaScript, C++, and C#. However, the same idea can be translated into other programming languages.

There are also other ways to achieve the same results. For example, instead of storing all updates, we could directly apply them to the rectangle matrix. This would make the getValue method faster, but would make the updateSubrectangle method slower. It would also use more memory as we need to store the updated rectangle.

In conclusion, always remember to consider your specific needs and constraints when choosing a solution. Consider the size of your data, the number of queries, memory usage, and performance requirements. And always strive to find a balance between these different factors.


Got a question? Ask the Teaching Assistant anything you don't understand.

Still not clear? Ask in the Forum,  Discord or Submit the part you don't understand to our editors.


TA 👨‍🏫