Skip to main content

Command Palette

Search for a command to run...

Solving Leetcode Interviews in Seconds with AI: Maximum Number of Points From Grid Queries

Updated
6 min read

Introduction

In this blog post, we will explore how to solve the LeetCode problem "2503" using AI. LeetCode is a popular platform for preparing for coding interviews, and with the help of AI tools like Chatmagic, we can generate solutions quickly and efficiently - helping you pass the interviews and get the job offer without having to study for months.

Problem Statement

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 <= 105 k == queries.length 1 <= k <= 104 1 <= grid[i][j], queries[i] <= 106

Explanation

Here's an efficient solution to the problem:

  • Sort Queries with Indices: Store the queries along with their original indices. Sort the queries in ascending order. This allows us to process the grid cells in increasing order of value, maximizing the number of reachable cells for smaller queries first and reusing this information for larger queries.

  • Disjoint Set Union (DSU) with Area Tracking: Use DSU to efficiently track connected components of cells whose values are less than the current query value. Maintain the area (number of cells) within each connected component.

  • Incremental Cell Addition: Iterate through the grid cells in increasing order of their values. For each cell, if its value is less than the current query value, add it to the DSU. When adding a cell, connect it to its adjacent neighbors that have already been added (i.e., their values are also less than the current grid value). The area of the connected component represents the number of reachable cells from the top-left cell.

  • Time & Space Complexity

    • Time Complexity: O(m*n*log(m*n) + k*log(k) + k*alpha(m*n)), where m and n are the dimensions of the grid, k is the number of queries, and alpha is the inverse Ackermann function (effectively constant for practical input sizes).
    • Space Complexity: O(m*n + k), due to the grid, DSU data structures, and the array to store results.

Code

    class DSU:
    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):
        root_x = self.find(x)
        root_y = self.find(y)
        if root_x != root_y:
            if self.size[root_x] < self.size[root_y]:
                root_x, root_y = root_y, root_x
            self.parent[root_y] = root_x
            self.size[root_x] += self.size[root_y]

class Solution:
    def hitBricks(self, grid: list[list[int]], hits: list[list[int]]) -> list[int]:
        m = len(grid)
        n = len(grid[0])

        def get_index(row, col):
            return row * n + col

        dsu = DSU(m * n + 1)
        roof_index = m * n 

        # Mark the bricks to be removed
        for row, col in hits:
            grid[row][col] -= 1

        # Connect the bricks to the roof, and neighbors
        for row in range(m):
            for col in range(n):
                if grid[row][col] == 1:
                    if row == 0:
                        dsu.union(roof_index, get_index(row, col))

                    # Connect to the neighbors
                    directions = [(0, 1), (0, -1), (1, 0), (-1, 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 grid[new_row][new_col] == 1:
                            dsu.union(get_index(row, col), get_index(new_row, new_col))

        result = []
        # Add bricks in reverse order
        for row, col in reversed(hits):
            grid[row][col] += 1

            if grid[row][col] == 1:
                prev_size = dsu.size[dsu.find(roof_index)]

                # Connect the brick to the roof, and neighbors
                if row == 0:
                    dsu.union(roof_index, get_index(row, col))

                directions = [(0, 1), (0, -1), (1, 0), (-1, 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 grid[new_row][new_col] == 1:
                        dsu.union(get_index(row, col), get_index(new_row, new_col))

                new_size = dsu.size[dsu.find(roof_index)]

                # Count the number of bricks that fell
                fallen_bricks = new_size - prev_size - 1
                result.append(max(0, fallen_bricks))
            else:
                result.append(0)

        return result[::-1] # Reverse the result

    def maximumMinimumPath(self, grid: list[list[int]]) -> int:
      rows, cols = len(grid), len(grid[0])
      pq = [(-grid[0][0], 0, 0)]  # Max-heap (negate for min value), row, col
      visited = set()
      min_path = grid[0][0]

      while pq:
          min_val, row, col = heapq.heappop(pq)
          min_val = -min_val
          min_path = min(min_path, min_val)

          if row == rows - 1 and col == cols - 1:
              return min_path

          if (row, col) in visited:
              continue
          visited.add((row, col))

          for dr, dc in [(0, 1), (0, -1), (1, 0), (-1, 0)]:
              new_row, new_col = row + dr, col + dc
              if 0 <= new_row < rows and 0 <= new_col < cols and (new_row, new_col) not in visited:
                  heapq.heappush(pq, (-grid[new_row][new_col], new_row, new_col))

    def maximumPoints(self, grid: list[list[int]], queries: list[int]) -> list[int]:
        m, n = len(grid), len(grid[0])
        k = len(queries)

        # Store queries with their original indices
        indexed_queries = sorted([(queries[i], i) for i in range(k)])
        sorted_queries = [q for q, _ in indexed_queries]
        indices = [i for _, i in indexed_queries]

        # Flatten grid with indices
        cells = sorted([(grid[i][j], i, j) for i in range(m) for j in range(n)])

        dsu = DSU(m * n)
        ans = [0] * k
        cell_idx = 0
        points = 0

        for i in range(k):
            query = sorted_queries[i]

            # Add cells smaller than the query value
            while cell_idx < m * n and cells[cell_idx][0] < query:
                _, row, col = cells[cell_idx]
                cell_index = row * n + col

                # Connect to neighbors
                for dr, dc in [(0, 1), (0, -1), (1, 0), (-1, 0)]:
                    new_row, new_col = row + dr, col + dc
                    if 0 <= new_row < m and 0 <= new_col < n:
                        neighbor_index = new_row * n + new_col
                        # Check if the neighbor has already been added
                        if grid[new_row][new_col] < query:
                            dsu.union(cell_index, neighbor_index)
                cell_idx += 1

            # Calculate points from top-left
            if grid[0][0] < query:
                ans[indices[i]] = dsu.size[dsu.find(0)]
            else:
                ans[indices[i]] = 0
        return ans

More from this blog

C

Chatmagic blog

2894 posts