Skip to content

Commit 5b2cb66

Browse files
authored
Merge pull request #164 from BrianLusina/feat/algorithms-dynamic-programming-unique-paths
feat(algorithms, dynamic-programming): unique paths
2 parents ffb76fd + 568cc9b commit 5b2cb66

File tree

2 files changed

+47
-66
lines changed

2 files changed

+47
-66
lines changed

algorithms/dynamic_programming/unique_paths/__init__.py

Lines changed: 22 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
from typing import Dict, Tuple
2-
2+
from functools import lru_cache
33

44
def unique_paths_math(m: int, n: int) -> int:
55
"""Uses math formula"""
@@ -28,6 +28,7 @@ def unique_paths_top_down(m: int, n: int) -> int:
2828
"""
2929
cache: Dict[Tuple[int, int], int] = {}
3030

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

5051

51-
def unique_paths_bottom_up(m: int, n: int) -> int:
52+
def unique_paths_bottom_up(rows: int, cols: int) -> int:
5253
"""Uses bottom-up approach
5354
54-
Complexity Analysis:
55-
- Time Complexity: O(m*n)
56-
- Space Complexity: O(n)
55+
Complexity Analysis
56+
57+
Time Complexity: O(m * n) where m and n are the dimensions of the grid. For both the recursive and iterative
58+
solutions, we need to fill in a table of size m x n.
59+
60+
Space Complexity: O(m * n) where m and n are the dimensions of the grid. The recursive solution uses O(m * n) space
61+
due to the call stack, while the iterative solution uses O(m * n) space for the dp table.
5762
"""
58-
row = [1] * n
63+
dp = [[0] * cols for _ in range(rows)]
5964

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

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

69-
for j in range(n - 2, -1, -1):
70-
# right value + value below
71-
new_row[j] = new_row[j + 1] + row[j]
72-
# update the row
73-
row = new_row
73+
# Fill in the rest of the table
74+
for i in range(1, rows):
75+
for j in range(1, cols):
76+
dp[i][j] = dp[i-1][j] + dp[i][j-1]
7477

75-
return row[0]
78+
return dp[rows-1][cols-1]

algorithms/dynamic_programming/unique_paths/test_unique_paths.py

Lines changed: 25 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -1,57 +1,35 @@
11
import unittest
2-
from . import unique_paths_top_down, unique_paths_math, unique_paths_bottom_up
3-
4-
5-
class UniquePathsTopDownTestCase(unittest.TestCase):
6-
def test_1(self):
7-
"""should return 28 for m=3 and n=7"""
8-
m = 3
9-
n = 7
10-
expected = 28
2+
from parameterized import parameterized
3+
from algorithms.dynamic_programming.unique_paths import (
4+
unique_paths_top_down,
5+
unique_paths_math,
6+
unique_paths_bottom_up,
7+
)
8+
9+
UNIQUE_PATHS_TEST_CASES = [
10+
(3, 7, 28),
11+
(3, 2, 3),
12+
(2, 2, 2),
13+
(3, 3, 6),
14+
(1, 1, 1),
15+
(10, 10, 48620),
16+
(20, 20, 35345263800),
17+
]
18+
19+
20+
class UniquePathsTestCase(unittest.TestCase):
21+
@parameterized.expand(UNIQUE_PATHS_TEST_CASES)
22+
def test_unique_paths_top_down(self, m: int, n: int, expected: int):
1123
actual = unique_paths_top_down(m, n)
1224
self.assertEqual(expected, actual)
1325

14-
def test_2(self):
15-
"""should return 3 for m=3 and n=2"""
16-
m = 3
17-
n = 2
18-
expected = 3
19-
actual = unique_paths_top_down(m, n)
20-
self.assertEqual(expected, actual)
21-
22-
23-
class UniquePathsBottomUpTestCase(unittest.TestCase):
24-
def test_1(self):
25-
"""should return 28 for m=3 and n=7"""
26-
m = 3
27-
n = 7
28-
expected = 28
26+
@parameterized.expand(UNIQUE_PATHS_TEST_CASES)
27+
def test_unique_paths_bottom_up(self, m: int, n: int, expected: int):
2928
actual = unique_paths_bottom_up(m, n)
3029
self.assertEqual(expected, actual)
3130

32-
def test_2(self):
33-
"""should return 3 for m=3 and n=2"""
34-
m = 3
35-
n = 2
36-
expected = 3
37-
actual = unique_paths_bottom_up(m, n)
38-
self.assertEqual(expected, actual)
39-
40-
41-
class UniquePathsMathTestCase(unittest.TestCase):
42-
def test_1(self):
43-
"""should return 28 for m=3 and n=7"""
44-
m = 3
45-
n = 7
46-
expected = 28
47-
actual = unique_paths_math(m, n)
48-
self.assertEqual(expected, actual)
49-
50-
def test_2(self):
51-
"""should return 3 for m=3 and n=2"""
52-
m = 3
53-
n = 2
54-
expected = 3
31+
@parameterized.expand(UNIQUE_PATHS_TEST_CASES)
32+
def test_unique_paths_math(self, m: int, n: int, expected: int):
5533
actual = unique_paths_math(m, n)
5634
self.assertEqual(expected, actual)
5735

0 commit comments

Comments
 (0)