Untitled

 avatar
unknown
python
2 years ago
5.2 kB
14
Indexable
from __future__ import annotations

'''
There is a robot on an m x n grid. The robot is initially located at the top-left corner (i.e., grid[0][0]). The robot 
tries to move to the bottom-right corner (i.e., grid[m - 1][n - 1]). The robot can only move either down or right at any 
point in time.

Given the two integers m and n, return the number of possible unique paths that the robot can take to reach the 
bottom-right corner.

Example 1:
Input: m = 3, n = 7
Output: 28

Example 2:
Input: m = 3, n = 2
Output: 3
Explanation: From the top-left corner, there are a total of 3 ways to reach the bottom-right corner:
1. Right -> Down -> Down
2. Down -> Down -> Right
3. Down -> Right -> Down

'''


# brute force
# time: O(2^(m + n))
# auxiliary O(m + n)
def unique_paths(m: int, n: int) -> int:
    return _unique_paths(0, 0, m, n)


def _unique_paths(i: int, j: int, m: int, n: int) -> int:
    if i > m or j > n:
        return 0
    if i == (m - 1) and j == (n - 1):
        return 1
    return _unique_paths(i + 1, j, m, n) + _unique_paths(i, j + 1, m, n)


###############


# Question 1. Solve the above problem using Top Down Dynamic Programming
def unique_paths_top_down(m: int, n: int) -> int:
    # recursion
    # memoization
    memo: dict[tuple[int, int], int] = {(0, 0): 0}
    memo.update({(row, 0): 1 for row in range(1, m)})
    memo.update({(0, col): 1 for col in range(1, n)})
    return _unique_paths_top_down(memo, 1, 1, m, n)


def _unique_paths_top_down(memo, i: int, j: int, m: int, n: int) -> int:
    if i > m or j > n:
        return 0
    if i == m and j == n:
        return 1
    if (m, n) not in memo:
        prev_up = 0
        if i > 0 and memo.get((i - 1, j)) is not None:
            prev_up = memo[(i - 1, j)]

        prev_left = 0
        if j > 0 and memo.get((i, j - 1)) is not None:
            prev_left = memo[(i, j - 1)]

        memo[(i, j)] = min(1, prev_up) + min(1, prev_left)

    return _unique_paths_top_down(memo, i + 1, j, m, n) + _unique_paths_top_down(memo, i, j + 1, m, n)


# Question 2. Solve the above problem using Bottom Up Dynamic Programming
# time: O(m * n)
# auxiliary O(m * x)
def unique_paths_bottom_up(m: int, n: int) -> int:
    # iteration
    # tabulation
    table = [[0 for _ in range(n)] for _ in range(m)]

    # first row
    for j in range(n):
        table[0][j] = 1

    # first column
    for i in range(m):
        table[i][0] = 1

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

    return table[m - 1][n - 1]


###############


# table space optimization
# time: O(m * n)
# auxiliary O(min(m, n))
def unique_paths_bottom_up_opt(m: int, n: int) -> int:
    table_min = [1 for _ in range(min(m, n))]

    for _ in range(1, max(m, n)):
        for a in range(1, min(m, n)):
            table_min[a] = table_min[a] + table_min[a - 1]

    return table_min[-1]


# lesser table space optimization with obstacles
# time: O(m * n)
# auxiliary O(m)
def unique_paths_bottom_up_opt_obst(obstacles: list[list[int]]) -> int:
    m = len(obstacles)
    n = len(obstacles[0])
    table_min = [1 for _ in range(m)]

    for i in range(1, n):
        for j in range(1, m):
            if obstacles[i][j] == 0:
                table_min[j] = table_min[j] + table_min[j - 1]
            else:
                table_min[j] = 0
    return table_min[-1]


# path lists
# time: O(n * m)
# auxiliary O(m * n)
# can use dfs queue w/ auxiliary O(n + m)
def unique_path_lists(m: int, n: int) -> int:
    agenda = [[(0, 0)]]
    count = 0
    while agenda:
        cur_path = agenda.pop()
        if cur_path[-1] == (m - 1, n - 1):
            count += 1
            continue
        # extend paths and update agenda
        if cur_path[-1][0] < m and cur_path[-1][1] < n:
            down = cur_path + [(cur_path[-1][0] + 1, cur_path[-1][1])]
            right = cur_path + [(cur_path[-1][0], cur_path[-1][1] + 1)]
            agenda += [down, right]
    return count


tests = [
    (3, 5),
    (5, 3),
    (1, 1),
    (2, 2),
    (1, 2),
    (2, 1),
    (3, 3),
    (5, 2),
    (4, 6),
    (1, 7),
    (5, 7),
]
for i, j in tests:
    print(i, unique_paths_bottom_up(i, j))
    print(i, unique_paths(i, j))
    print(i, unique_paths_top_down(i, j))
    print(i, unique_path_lists(i, j))

    print(i, unique_paths_bottom_up_opt(i, j))
    print('----')

matrices = {
    'obst_3x3': [
        [0, 0, 0],
        [0, 1, 0],
        [0, 0, 0],
    ],
    'obst_5x5a': [
        [0, 0, 0, 0, 0],
        [0, 0, 0, 0, 0],
        [0, 0, 1, 0, 0],
        [0, 0, 0, 0, 0],
        [0, 0, 0, 0, 0],
    ],
    'obst_5x5b': [
        [0, 0, 0, 0, 0],
        [0, 0, 0, 0, 0],
        [0, 0, 1, 0, 0],
        [0, 0, 0, 1, 0],
        [0, 0, 0, 0, 0],
    ],
    'obst_5x5c': [
        [0, 0, 0, 0, 0],
        [0, 0, 0, 1, 0],
        [0, 0, 1, 0, 0],
        [0, 1, 0, 1, 0],
        [0, 0, 0, 0, 0],
    ],
}
for k, v in matrices.items():
    print(k, v, unique_paths_bottom_up_opt_obst(v))
Editor is loading...