Skip to content

Commit 29e3f21

Browse files
committed
AlgoExpert Number Of Ways To Traverse Graph
1 parent ebc0538 commit 29e3f21

File tree

3 files changed

+112
-15
lines changed

3 files changed

+112
-15
lines changed

README.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1169,6 +1169,7 @@ First column links to the problem in AlgoExpert, second is the problem's difficu
11691169
| [Non Constructible Change][ae&non-constructible-change] | 🟢 Easy | [![python](res/py.png)][ae&non-constructible-change#py] |
11701170
| [Nth-Fibonacci][ae&nth-fibonacci] | 🟢 Easy | [![python](res/py.png)][ae&nth-fibonacci#py] |
11711171
| [Number Of Ways To Make Change][ae&number-of-ways-to-make-change] | 🟠 Medium | [![python](res/py.png)][ae&number-of-ways-to-make-change#py] |
1172+
| [Number Of Ways To Traverse Graph][ae&number-of-ways-to-traverse-graph] | 🟠 Medium | [![python](res/py.png)][ae&number-of-ways-to-traverse-graph#py] |
11721173
| [Palindrome Check][ae&palindrome-check] | 🟢 Easy | [![python](res/py.png)][ae&palindrome-check#py] |
11731174
| [Permutations][ae&permutations] | 🟠 Medium | [![python](res/py.png)][ae&permutations#py] |
11741175
| [Product Sum][ae&product-sum] | 🟢 Easy | [![python](res/py.png)][ae&product-sum#py] |
@@ -1290,6 +1291,8 @@ First column links to the problem in AlgoExpert, second is the problem's difficu
12901291
[ae&nth-fibonacci#py]: algoexpert/nth-fibonacci.py
12911292
[ae&number-of-ways-to-make-change]: https://www.algoexpert.io/questions/number-of-ways-to-make-change
12921293
[ae&number-of-ways-to-make-change#py]: algoexpert/number-of-ways-to-make-change.py
1294+
[ae&number-of-ways-to-traverse-graph]: https://www.algoexpert.io/questions/number-of-ways-to-traverse-graph
1295+
[ae&number-of-ways-to-traverse-graph#py]: algoexpert/number-of-ways-to-traverse-graph.py
12931296
[ae&palindrome-check]: https://www.algoexpert.io/questions/palindrome-check
12941297
[ae&palindrome-check#py]: algoexpert/palindrome-check.py
12951298
[ae&permutations]: https://www.algoexpert.io/questions/permutations
Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
1+
# Number Of Ways To Traverse Graph
2+
# 🟠 Medium
3+
#
4+
# https://www.algoexpert.io/questions/number-of-ways-to-traverse-graph
5+
#
6+
# Tags: Graph - Dynamic Programming - Math
7+
8+
import timeit
9+
from math import factorial
10+
11+
12+
# We can look at this problem as computing the ways to arrange m "R" and
13+
# n "D" in a string of length n+m, where m is the width of the graph and
14+
# n is its height, at each position we can choose to go either right or
15+
# down until we have consumed all the moves. When we look at the problem
16+
# that way, it is easy to see that it can be expressed as a classic
17+
# combination problem; the problem of traveling a graph of w: 4, h: 3,
18+
# from its top-left corner to its bottom-right corner only moving down
19+
# or right at each step, is the problem of choosing where to place two
20+
# "D"s in a string of 3 "R"s and 2 "D"s, which is C(5,2) (C(5,3)).
21+
#
22+
# https://betterexplained.com/articles/easy-permutations-and-combinations/
23+
#
24+
# Time complexity: O(log(n)) - the factorial function is calculated as:
25+
#
26+
# > factorial(n) is written in the form 2**k * m, with m odd. k and m
27+
# > are computed separately, and then combined using a left shift.
28+
#
29+
# Python3 factorial implementation and comments:
30+
# https://hg.python.org/cpython/file/d42f264f291e/Modules/mathmodule.c#l1218
31+
#
32+
# Space complexity: O(1) - But the result of factorial grows at a O(n!)
33+
class Math:
34+
def numberOfWaysToTraverseGraph(self, width: int, height: int) -> int:
35+
a, b = width - 1, height - 1
36+
return factorial(a + b) // (factorial(a) * factorial(b))
37+
38+
39+
# Bottom-up DP. We initialize a row of size m with all values being
40+
# equal to 1. This represents the possible ways to reach the destination
41+
# from these positions. Then we iterate over the remaining rows of the
42+
# matrix updating the possible ways to reach the destination from the
43+
# given position.
44+
#
45+
# For each position, the possible ways to reach the destination are the
46+
# sum of the position above plus the position to its left.
47+
#
48+
# Time complexity: O(m*n) - We do one calculation for each position of
49+
# the matrix mxn.
50+
# Space complexity: O(n) - We store an array of size n in memory.
51+
class DP:
52+
def numberOfWaysToTraverseGraph(self, width: int, height: int) -> int:
53+
paths = [1] * height
54+
for _ in range(width - 1):
55+
for i in range(height):
56+
# The first col is always 1 because we can only move
57+
# down from this position to reach the target.
58+
if i > 0:
59+
# The number of ways to reach the target from here
60+
# are the sum of the ways to reach the target
61+
# from the position to the right and the position
62+
# below (inverted on the code for clarity).
63+
paths[i] += paths[i - 1]
64+
65+
return paths[-1]
66+
67+
68+
def test():
69+
executors = [
70+
Math,
71+
DP,
72+
]
73+
tests = [
74+
[1, 1, 1],
75+
[2, 1, 1],
76+
[3, 2, 3],
77+
[2, 3, 3],
78+
[4, 3, 10],
79+
[3, 7, 28],
80+
[5, 9, 495],
81+
]
82+
for executor in executors:
83+
start = timeit.default_timer()
84+
for _ in range(1):
85+
for col, t in enumerate(tests):
86+
sol = executor()
87+
result = sol.numberOfWaysToTraverseGraph(t[0], t[1])
88+
exp = t[2]
89+
assert result == exp, (
90+
f"\033[93m» {result} <> {exp}\033[91m for"
91+
+ f" test {col} using \033[1m{executor.__name__}"
92+
)
93+
stop = timeit.default_timer()
94+
used = str(round(stop - start, 5))
95+
cols = "{0:20}{1:10}{2:10}"
96+
res = cols.format(executor.__name__, used, "seconds")
97+
print(f"\033[92m» {res}\033[0m")
98+
99+
100+
test()

leetcode/unique-paths.py

Lines changed: 9 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -27,10 +27,8 @@
2727
# the matrix mxn.
2828
# Space complexity: O(n) - we store an array of size n in memory.
2929
#
30-
# Runtime: 42 ms, faster than 69.04% of Python3 online submissions for
31-
# Unique Paths.
32-
# Memory Usage: 14 MB, less than 32.87% of Python3 online submissions
33-
# for Unique Paths.
30+
# Runtime: 42 ms, faster than 69.04%
31+
# Memory Usage: 14 MB, less than 32.87%
3432
class DP:
3533
def uniquePaths(self, m: int, n: int) -> int:
3634
paths = [1] * n
@@ -74,10 +72,8 @@ def uniquePaths(self, m: int, n: int) -> int:
7472
# at least, I expected the code to run fast
7573
# but use a lot of memory, but the result is the opposite.
7674
#
77-
# Runtime: 71 ms, faster than 7.38% of Python3 online submissions for
78-
# Unique Paths.
79-
# Memory Usage: 13.7 MB, less than 97.78% of Python3 online submissions
80-
# for Unique Paths.
75+
# Runtime: 71 ms, faster than 7.38%
76+
# Memory Usage: 13.7 MB, less than 97.78%
8177
class Math:
8278
def uniquePaths(self, m: int, n: int) -> int:
8379
down = m - 1
@@ -93,10 +89,8 @@ def uniquePaths(self, m: int, n: int) -> int:
9389
# Space complexity: O(m*n) - the memory used by reduce and the lambda
9490
# will grow linearly with the size of the matrix.
9591
#
96-
# Runtime: 41 ms, faster than 71.79% of Python3 online submissions for
97-
# Unique Paths.
98-
# Memory Usage: 13.9 MB, less than 73.29% of Python3 online submissions
99-
# for Unique Paths.
92+
# Runtime: 41 ms, faster than 71.79%
93+
# Memory Usage: 13.9 MB, less than 73.29%
10094
class Reduce:
10195
def uniquePaths(self, m: int, n: int) -> int:
10296
if 1 in [n, m]:
@@ -110,17 +104,17 @@ def test():
110104
executors = [DP, Math, Reduce]
111105
tests = [
112106
[1, 1, 1],
107+
[3, 2, 3],
108+
[3, 7, 28],
113109
[
114110
100,
115111
100,
116112
22750883079422934966181954039568885395604168260154104734000,
117113
],
118-
[3, 7, 28],
119-
[3, 2, 3],
120114
]
121115
for executor in executors:
122116
start = timeit.default_timer()
123-
for _ in range(int(float("1"))):
117+
for _ in range(1):
124118
for i, t in enumerate(tests):
125119
sol = executor()
126120
result = sol.uniquePaths(t[0], t[1])

0 commit comments

Comments
 (0)