Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
41 changes: 22 additions & 19 deletions algorithms/dynamic_programming/unique_paths/__init__.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
from typing import Dict, Tuple

from functools import lru_cache

def unique_paths_math(m: int, n: int) -> int:
"""Uses math formula"""
Expand Down Expand Up @@ -28,6 +28,7 @@ def unique_paths_top_down(m: int, n: int) -> int:
"""
cache: Dict[Tuple[int, int], int] = {}

@lru_cache(None)
def unique_path_helper(row: int, col: int) -> int:
# If already calculated, re-use those
if (row, col) in cache:
Expand All @@ -48,28 +49,30 @@ def unique_path_helper(row: int, col: int) -> int:
return unique_path_helper(m, n)


def unique_paths_bottom_up(m: int, n: int) -> int:
def unique_paths_bottom_up(rows: int, cols: int) -> int:
"""Uses bottom-up approach

Complexity Analysis:
- Time Complexity: O(m*n)
- Space Complexity: O(n)
Complexity Analysis

Time Complexity: O(m * n) where m and n are the dimensions of the grid. For both the recursive and iterative
solutions, we need to fill in a table of size m x n.

Space Complexity: O(m * n) where m and n are the dimensions of the grid. The recursive solution uses O(m * n) space
due to the call stack, while the iterative solution uses O(m * n) space for the dp table.
"""
row = [1] * n
dp = [[0] * cols for _ in range(rows)]

# go through all rows except the last one
for i in range(m - 1):
new_row = [1] * n
# Set base case: there is only one way to reach any cell in the first row (moving only right)
for r in range(cols):
dp[0][r] = 1

# go through every column except the right most column
# because the last value in every row is 1
# start at second to last position and
# keep going until we get to the beginning (reverse order)
# Set base case: there is only one way to reach any cell in the first column (moving only down)
for r in range(rows):
dp[r][0] = 1

for j in range(n - 2, -1, -1):
# right value + value below
new_row[j] = new_row[j + 1] + row[j]
# update the row
row = new_row
# Fill in the rest of the table
for i in range(1, rows):
for j in range(1, cols):
dp[i][j] = dp[i-1][j] + dp[i][j-1]

return row[0]
return dp[rows-1][cols-1]
72 changes: 25 additions & 47 deletions algorithms/dynamic_programming/unique_paths/test_unique_paths.py
Original file line number Diff line number Diff line change
@@ -1,57 +1,35 @@
import unittest
from . import unique_paths_top_down, unique_paths_math, unique_paths_bottom_up


class UniquePathsTopDownTestCase(unittest.TestCase):
def test_1(self):
"""should return 28 for m=3 and n=7"""
m = 3
n = 7
expected = 28
from parameterized import parameterized
from algorithms.dynamic_programming.unique_paths import (
unique_paths_top_down,
unique_paths_math,
unique_paths_bottom_up,
)

UNIQUE_PATHS_TEST_CASES = [
(3, 7, 28),
(3, 2, 3),
(2, 2, 2),
(3, 3, 6),
(1, 1, 1),
(10, 10, 48620),
(20, 20, 35345263800),
]


class UniquePathsTestCase(unittest.TestCase):
@parameterized.expand(UNIQUE_PATHS_TEST_CASES)
def test_unique_paths_top_down(self, m: int, n: int, expected: int):
actual = unique_paths_top_down(m, n)
self.assertEqual(expected, actual)

def test_2(self):
"""should return 3 for m=3 and n=2"""
m = 3
n = 2
expected = 3
actual = unique_paths_top_down(m, n)
self.assertEqual(expected, actual)


class UniquePathsBottomUpTestCase(unittest.TestCase):
def test_1(self):
"""should return 28 for m=3 and n=7"""
m = 3
n = 7
expected = 28
@parameterized.expand(UNIQUE_PATHS_TEST_CASES)
def test_unique_paths_bottom_up(self, m: int, n: int, expected: int):
actual = unique_paths_bottom_up(m, n)
self.assertEqual(expected, actual)

def test_2(self):
"""should return 3 for m=3 and n=2"""
m = 3
n = 2
expected = 3
actual = unique_paths_bottom_up(m, n)
self.assertEqual(expected, actual)


class UniquePathsMathTestCase(unittest.TestCase):
def test_1(self):
"""should return 28 for m=3 and n=7"""
m = 3
n = 7
expected = 28
actual = unique_paths_math(m, n)
self.assertEqual(expected, actual)

def test_2(self):
"""should return 3 for m=3 and n=2"""
m = 3
n = 2
expected = 3
@parameterized.expand(UNIQUE_PATHS_TEST_CASES)
def test_unique_paths_math(self, m: int, n: int, expected: int):
actual = unique_paths_math(m, n)
self.assertEqual(expected, actual)

Expand Down
Loading