1219. Path with Maximum Gold

1219. Path with Maximum Gold

14th May 2024 LeetCode Daily

ยท

5 min read

TL;DR

The problem involves finding the maximum amount of gold that can be collected from a grid-based gold mine, following specific movement and cell visitation rules. The solution uses recursive backtracking to explore all potential paths, ensuring each cell is visited only once and avoiding cells with zero gold. Time complexity is (O(4^{(mn)})) and space complexity is (O(mn)).

Problem Statement

In a gold mine grid of size m x n, each cell in this mine has an integer representing the amount of gold in that cell, 0 if it is empty.

Return the maximum amount of gold you can collect under the conditions:

  • Every time you are located in a cell you will collect all the gold in that cell.

  • From your position, you can walk one step to the left, right, up, or down.

  • You can't visit the same cell more than once.

  • Never visit a cell with 0 gold.

  • You can start and stop collecting gold from any position in the grid that has some gold.

Examples

Example 1:

Input: grid = [[0,6,0],[5,8,7],[0,9,0]]
Output: 24
Explanation:
[[0,6,0],
 [5,8,7],
 [0,9,0]]
Path to get the maximum gold, 9 -> 8 -> 7.

Example 2:

Input: grid = [[1,0,7],[2,0,6],[3,4,5],[0,3,0],[9,0,20]]
Output: 28
Explanation:
[[1,0,7],
 [2,0,6],
 [3,4,5],
 [0,3,0],
 [9,0,20]]
Path to get the maximum gold, 1 -> 2 -> 3 -> 4 -> 5 -> 6 -> 7.

Constraints

  • m == grid.length

  • n == grid[i].length

  • 1 <= m, n <= 15

  • 0 <= grid[i][j] <= 100

  • There are at most 25 cells containing gold.

Intuition

Using Recursive Backtracking

Recursive backtracking is a problem-solving technique that can be applied to various scenarios, particularly those involving finding all possible solutions or paths within a constrained space. Here's a detailed explanation of this approach:

Core Idea:

  1. Recursion: The primary concept involves breaking down the problem into smaller sub-problems and solving them recursively. Each sub-problem represents a potential path or solution branch.

  2. Backtracking: As the algorithm explores a sub-problem, it keeps track of the choices made (decisions) along the way. If a sub-problem leads to a dead end (no valid solution), the algorithm backtracks to the previous decision point and tries a different path (choice).

This is how we can use recursive backtracking for this problem:

  1. Base Case: The base case for findMaxGold is when the current move is invalid (out of bounds or no gold) or when you've reached the end of a path (no valid moves left). In both cases, the function returns 0 (no gold collected).

  2. Recursive Calls: For each valid direction (up, down, left, right), the function makes a recursive call with the updated row and column positions. This explores all potential paths starting from the current cell.

  3. Decision Making: The decision here is to explore each valid direction as a potential path extension and calculate the maximum gold obtainable from that path.

  4. Tracking and Backtracking: We keep track of visited cells by setting their gold value to 0. This prevents revisiting and essentially acts like backtracking if a path leads to no further gold.

Advantages of Recursive Backtracking:

  • Elegance: It can provide an elegant and concise solution for problems with a clear structure of sub-problems and choices.

  • Flexibility: It can be adapted to various problems by defining appropriate base cases, recursive calls, and decision-making logic.

Disadvantages of Recursive Backtracking:

  • Complexity: In some cases, the number of recursive calls can explode, leading to exponential time complexity. This can be mitigated by techniques like memoization(storing solutions to sub-problems to avoid redundant calculations).

  • Stack Overflow: Deep recursion can lead to stack overflow errors, especially for problems with a large number of potential paths.

When to Use Recursive Backtracking:

  • Finding all possible solutions or paths within a constrained space (e.g., mazes, permutations, combinations).

  • Solving constraint satisfaction problems (e.g., Sudoku, crossword puzzles).

  • Graph traversal problems (e.g., finding all connected components in a graph).

Code Solution

# TC: O(4^(m*n)), as we go to 4 directions for each cell visited
# SC: O(m*n), size of recursive call stack
class Solution:
    def getMaximumGold(self, grid: List[List[int]]) -> int:
        # initialize length of row as m
        # and length of columns as n
        m,n=len(grid),len(grid[0])
        # initialize max gold as 0
        max_gold=0
        # iterate in range of rows
        for row in range(m):
            # iterate n range of columns
            for col in range(n):
                # only check for max_gold if current element is not zero
                if grid[row][col]!=0:
                    # find amximum gold by calling recursive function findMaxGold
                    max_gold=max(max_gold,self.findMaxGold(grid,row,col,m,n))
        # return the max_gold found
        return max_gold

    def findMaxGold(self,grid:List[List[int]],row:int,col:int,m:int,n:int):
        # check if the current move is valid
        if not self.isValidMove(grid,m,n,row,col):
            return 0
        # initialize current gold as gold in the current index of grid
        current_gold=grid[row][col]
        # make the current index as zero
        grid[row][col]=0
        # initialize maxGold for this position to curent_gold
        localMaxGold=current_gold
        # initialize arrays of valid moves fro up,down,left and right.
        valid_rows=[-1,1,0,0]
        valid_cols=[0,0,1,-1]
        # iterate over valid moves
        for index in range(4):
            # find new row as x
            x=row+valid_rows[index]
            # find new column as y
            y=col+valid_cols[index]
            # call findMaxGold again to go to next position for x and y
            # then take max of localMaxGold and current_gold+recursive call
            # the recursive call will find all the valid moves and get the gold collected for them
            localMaxGold=max(localMaxGold,current_gold+self.findMaxGold(grid,x,y,m,n))
        # initialize current_gold back to current index
        # as this index will be used by some other call to findMaxGold
        grid[row][col]=current_gold
        # return the localMaxGold if we started from current row and column
        return localMaxGold

    def isValidMove(self,grid:List[List[int]],m:int,n:int,x:int,y:int)->bool:
        # check out of bounds and if current element of grid is zero
        if x<0 or x>=m or y<0 or y>=n or grid[x][y]==0:
            # return False if the move is invalid or current element is zero
            return False
        # else return True
        return True

Time and Space Complexity

Time Complexity:

$$O(4^{(m*n)})$$

This is because we try to find the max gold in each of the four directions we can go. Then, we move through the grid from those new positions again.

Space Complexity:

$$O(m*n)$$

This is the maximum size of the recursive call stack.

Conclusion

ย