Taro Logo

Maximum Number of Points From Grid Queries

Medium
13 views
2 months ago

You are given an m x n integer matrix grid and an array queries of size k. Find an array answer of size k such that for each integer queries[i] you start in the top left cell of the matrix and repeat the following process:

  • If queries[i] is strictly greater than the value of the current cell that you are in, then you get one point if it is your first time visiting this cell, and you can move to any adjacent cell in all 4 directions: up, down, left, and right.
  • Otherwise, you do not get any points, and you end this process.

After the process, answer[i] is the maximum number of points you can get. Note that for each query you are allowed to visit the same cell multiple times.

Return the resulting array answer.

Example 1:

Input: grid = [[1,2,3],[2,5,7],[3,5,1]], queries = [5,6,2]
Output: [5,8,1]
Explanation: The diagrams above show which cells we visit to get points for each query.

Example 2:

Input: grid = [[5,2,1],[1,1,2]], queries = [3]
Output: [0]
Explanation: We can not get any points because the value of the top left cell is already greater than or equal to 3.

Constraints:

  • m == grid.length
  • n == grid[i].length
  • 2 <= m, n <= 1000
  • 4 <= m * n <= 10^5
  • k == queries.length
  • 1 <= k <= 10^4
  • 1 <= grid[i][j], queries[i] <= 10^6
Sample Answer
class Solution:
    def maxPoints(self, grid: list[list[int]], queries: list[int]) -> list[int]:
        m, n = len(grid), len(grid[0])
        directions = [(0, 1), (0, -1), (1, 0), (-1, 0)]

        def bfs(threshold: int) -> int:
            visited = [[False] * n for _ in range(m)]
            q = [(0, 0)]
            visited[0][0] = True
            points = 0

            if grid[0][0] >= threshold:
                return 0
            
            points = 1

            while q:
                row, col = q.pop(0)

                for dr, dc in directions:
                    new_row, new_col = row + dr, col + dc

                    if 0 <= new_row < m and 0 <= new_col < n and not visited[new_row][new_col] and grid[new_row][new_col] < threshold:
                        visited[new_row][new_col] = True
                        q.append((new_row, new_col))
                        points += 1

            return points

        answer = []
        for query in queries:
            answer.append(bfs(query))

        return answer

Brute Force Approach

The provided code implements a brute-force approach using Breadth-First Search (BFS) to determine the maximum number of points obtainable for each query. It directly simulates the problem statement by exploring the grid for each query.

Optimized Solution (Using Offline Queries and Union Find)

To optimize, we can use an offline query processing approach combined with the Union-Find data structure.

  1. Sort Queries: Sort the queries along with their original indices.
  2. Sort Grid Cells: Sort all grid cells by their values.
  3. Union-Find: Iterate through the sorted grid cells. For each cell, merge it with its adjacent cells if their values are smaller than the current query value. Use Union-Find to keep track of connected components.
  4. Calculate Area: The size of the connected component starting from (0, 0) gives the number of reachable cells.
  5. Store Results: Store the results based on the original indices of the queries.
class UnionFind:
    def __init__(self, n):
        self.parent = list(range(n))
        self.size = [1] * n
        
    def find(self, x):
        if self.parent[x] != x:
            self.parent[x] = self.find(self.parent[x])
        return self.parent[x]
        
    def union(self, x, y):
        rootX = self.find(x)
        rootY = self.find(y)
        if rootX != rootY:
            if self.size[rootX] < self.size[rootY]:
                rootX, rootY = rootY, rootX
            self.parent[rootY] = rootX
            self.size[rootX] += self.size[rootY]

class Solution:
    def maxPoints(self, grid: list[list[int]], queries: list[int]) -> list[int]:
        m, n = len(grid), len(grid[0])
        cells = []
        for i in range(m):
            for j in range(n):
                cells.append((grid[i][j], i, j))
        cells.sort()

        queries_with_indices = sorted([(queries[i], i) for i in range(len(queries))])
        answers = [0] * len(queries)

        uf = UnionFind(m * n)
        directions = [(0, 1), (0, -1), (1, 0), (-1, 0)]
        
        k = 0
        visited = [[False] * n for _ in range(m)]
        reachable = 0

        for query, index in queries_with_indices:
            while k < len(cells) and cells[k][0] < query:
                _, row, col = cells[k]
                idx = row * n + col
                visited[row][col] = True
                if row == 0 and col == 0:
                    reachable = uf.size[uf.find(idx)]

                for dr, dc in directions:
                    new_row, new_col = row + dr, col + dc
                    if 0 <= new_row < m and 0 <= new_col < n and visited[new_row][new_col]:
                        new_idx = new_row * n + new_col
                        uf.union(idx, new_idx)
                if visited[0][0]:
                     reachable = uf.size[uf.find(0)]

                k += 1
            answers[index] = reachable

        return answers

Time Complexity Analysis

  • Brute Force (BFS): O(k * m * n), where k is the number of queries, and m * n is the size of the grid. For each query, we potentially visit all cells in the grid.
  • Optimized (Union-Find): O(m * n * log(m * n) + k * log(k) + m * n * α(m * n)), where k is the number of queries and α is the inverse Ackermann function, which grows very slowly and can be considered almost constant. Sorting the cells takes O(m * n * log(m * n)), sorting the queries takes O(k * log(k)), and the Union-Find operations take O(m * n * α(m * n)).

Space Complexity Analysis

  • Brute Force (BFS): O(m * n) for the visited array and queue in BFS.
  • Optimized (Union-Find): O(m * n) for the Union-Find data structure, visited array, and storing the sorted cells.

Edge Cases

  1. Empty Grid: If the grid is empty (m=0 or n=0), return an empty list.
  2. Empty Queries: If the queries list is empty, return an empty list.
  3. Query Value Less Than or Equal to Grid[0][0]: If a query value is less than or equal to the starting cell's value, the answer for that query is 0.
  4. Large Query Values: If a query value is much larger than all the grid values, the algorithm should still correctly identify all reachable cells.
  5. Grid with Identical Values: The algorithm should work correctly even if the grid contains many cells with the same value.
  6. Disconnected Grid: Even if not all cells are reachable from (0, 0), the algorithm should correctly count the reachable cells for each query.