|
| 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() |
0 commit comments