Skip to content

DP Grid Basics

Table of Contents

64. Minimum Path Sum

from functools import cache
from typing import List


# Iterative
def minPathSum(grid: List[List[int]]) -> int:
    m, n = len(grid), len(grid[0])

    # init
    dp = [[0] * n for _ in range(m)]
    dp[0][0] = grid[0][0]

    for i in range(1, m):
        dp[i][0] = grid[i][0] + dp[i - 1][0]
    for j in range(1, n):
        dp[0][j] = grid[0][j] + dp[0][j - 1]

    # update
    for i in range(1, m):
        for j in range(1, n):
            dp[i][j] = grid[i][j] + min(dp[i - 1][j], dp[i][j - 1])

    return dp[-1][-1]


# Recursive
def minPathSumDFS(grid: List[List[int]]) -> int:
    INF = 10**18

    @cache
    def dfs(i, j):
        if i < 0 or j < 0:
            return INF
        if i == 0 and j == 0:
            return grid[i][j]
        return min(dfs(i, j - 1), dfs(i - 1, j)) + grid[i][j]

    return dfs(len(grid) - 1, len(grid[0]) - 1)


grid = [[1, 3, 1], [1, 5, 1], [4, 2, 1]]
print(minPathSum(grid))  # 7
print(minPathSumDFS(grid))  # 7

62. Unique Paths

"""
-   Count the number of unique paths to reach the bottom-right corner of a `m x n` grid.

![62](https://assets.leetcode.com/uploads/2018/10/22/robot_maze.png)
"""


# DP - 2D
def uniquePaths(m: int, n: int) -> int:
    if m == 1 or n == 1:
        return 1

    dp = [[1] * n for _ in range(m)]

    for i in range(1, m):
        for j in range(1, n):
            dp[i][j] = dp[i - 1][j] + dp[i][j - 1]

    return dp[-1][-1]


print(uniquePaths(m=3, n=7))  # 28
# [[1, 1, 1,  1,  1,  1,  1],
#  [1, 2, 3,  4,  5,  6,  7],
#  [1, 3, 6, 10, 15, 21, 28]]
#include <iostream>
#include <vector>
using namespace std;

int uniquePaths(int m, int n) {
    vector dp(m, vector<int>(n, 1));

    for (int i = 1; i < m; i++) {
        for (int j = 1; j < n; j++) {
            dp[i][j] = dp[i - 1][j] + dp[i][j - 1];
        }
    }

    return dp[m - 1][n - 1];
}

int main() {
    int m = 3, n = 7;
    cout << uniquePaths(m, n) << endl;  // 28
    return 0;
}

63. Unique Paths II

from functools import cache
from typing import List


class UniquePathsWithObstacles:
    def memoization(self, obstacleGrid: List[List[int]]) -> int:
        """
        Approach: DFS with Memoization
        Time complexity: O(mn).
        Space complexity: O(mn).
        """

        @cache
        def dfs(i: int, j: int) -> int:
            if i < 0 or j < 0 or obstacleGrid[i][j]:
                return 0
            if i == 0 and j == 0:
                return 1
            return dfs(i - 1, j) + dfs(i, j - 1)

        m, n = len(obstacleGrid), len(obstacleGrid[0])
        return dfs(m - 1, n - 1)

    def dp_2d(self, obstacleGrid: List[List[int]]) -> int:
        """
        Approach: 2D Dynamic Programming
        Time complexity: O(mn).
        Space complexity: O(mn).
        """
        # edge cases
        if obstacleGrid[0][0] == 1 or obstacleGrid[-1][-1] == 1:
            return 0

        m, n = len(obstacleGrid), len(obstacleGrid[0])

        # init dp
        dp = [[0] * n for _ in range(m)]

        for i in range(m):
            if obstacleGrid[i][0] == 0:
                dp[i][0] = 1
            else:
                break

        for j in range(n):
            if obstacleGrid[0][j] == 0:
                dp[0][j] = 1
            else:
                break

        # update dp
        for i in range(1, m):
            for j in range(1, n):
                if obstacleGrid[i][j] == 1:
                    continue
                dp[i][j] = dp[i - 1][j] + dp[i][j - 1]

        return dp[-1][-1]

    def dp_1d(self, obstacleGrid: List[List[int]]) -> int:
        """
        Approach: 1D Dynamic Programming
        Time complexity: O(mn).
        Space complexity: O(n).
        """
        n = len(obstacleGrid[0])
        dp = [0] * (n + 1)
        dp[1] = 1

        for row in obstacleGrid:
            for j, x in enumerate(row):
                if x == 0:
                    dp[j + 1] += dp[j]
                else:
                    dp[j + 1] = 0
        return dp[n]


if __name__ == "__main__":
    solution = UniquePathsWithObstacles()
    obstacleGrid = [[0, 0, 0], [0, 1, 0], [0, 0, 0]]
    assert solution.dp_2d(obstacleGrid) == 2
    assert solution.memoization(obstacleGrid) == 2
    assert solution.dp_1d(obstacleGrid) == 2

120. Triangle

from functools import cache
from typing import List


class Solution:
    # Recursive
    def minimumTotalRecursive(self, triangle: List[List[int]]) -> int:
        n = len(triangle)

        @cache
        def dfs(i, j):
            if i == n - 1:
                return triangle[i][j]
            return min(dfs(i + 1, j), dfs(i + 1, j + 1)) + triangle[i][j]

        return dfs(0, 0)

    # Iterative
    def minimumTotalIterative(self, triangle: List[List[int]]) -> int:
        for i in range(len(triangle) - 2, -1, -1):
            for j in range(i + 1):
                triangle[i][j] += min(triangle[i + 1][j], triangle[i + 1][j + 1])
        return triangle[0][0]


if __name__ == "__main__":
    solution = Solution()
    triangle = [[2], [3, 4], [6, 5, 7], [4, 1, 8, 3]]
    assert solution.minimumTotalRecursive(triangle) == 11
    assert solution.minimumTotalIterative(triangle) == 11

3393. Count Paths With the Given XOR Value

  • LeetCode | 力扣

  • Tags: Array, Dynamic Programming, Bit Manipulation, Matrix

931. Minimum Falling Path Sum

2684. Maximum Number of Moves in a Grid

from typing import List


# DFS
def maxMovesDFS(grid: List[List[int]]) -> int:
    m, n = len(grid), len(grid[0])
    res = 0

    def dfs(r, c):
        nonlocal res
        res = max(res, c)
        if res == n - 1:
            return

        for k in r - 1, r, r + 1:
            if 0 <= k < m and grid[k][c + 1] > grid[r][c]:
                dfs(k, c + 1)
        grid[r][c] = 0

    for i in range(m):
        dfs(i, 0)

    return res


grid = [[2, 4, 3, 5], [5, 4, 9, 3], [3, 4, 2, 11], [10, 9, 13, 15]]
print(maxMovesDFS(grid))  # 3

2304. Minimum Path Cost in a Grid

1289. Minimum Falling Path Sum II

3418. Maximum Amount of Money Robot Can Earn