|
| 1 | +""" |
| 2 | +Visit every 0 cell once: tree? Can't have cycles. |
| 3 | +Hamiltonian path: visits each vertex exactly once; needs to start at 1, end at 0, and not visit -1. |
| 4 | +Start at 2? does that help at all? |
| 5 | +max cells - 400 (20 x 20); n^2 is 160000, so this is viable |
| 6 | +
|
| 7 | +=== Brute force / DFS approach === |
| 8 | +- Count number of -1 beforehand |
| 9 | +- Do regular DFS starting at 1, except can't visit -1, can't revisit, and can't visit 2 unless n*m-(count of -1) cells have been visited |
| 10 | +- Return 1 if we reach 2, return 0 otherwise |
| 11 | +- Can we do this but prune? |
| 12 | +
|
| 13 | +=== DP approach === |
| 14 | +- "Hamiltonian Zero Path" (HZP): path in grid starting at start, reaching target cell, crossing every 0 only once. |
| 15 | +- Num of HZP(1,2): sum of HZP(1, neighbor) for each of 2's neighbors (each is unique since they end at a different neighbor) |
| 16 | + - This doesn't hold for a 2x2 -> 3x3 grid; adding cells changes whether a previously reachable cell is reachable or not. |
| 17 | +- 1x1 is not valid input. 2x1, 1x2 are trivial; 1 path. |
| 18 | +- 2x2 has one allowed for only one graph: |
| 19 | + [1, 2] |
| 20 | + [0, 0] (D-R-U) |
| 21 | + - Any other arrangement (not counting rotations) has no solution |
| 22 | + - Any 2x2 with nonzero -1s has no solution |
| 23 | +
|
| 24 | +This has 4 ways to make acyclic paths of len 12. |
| 25 | +[1, 0, 0, 0] |
| 26 | +[0, 0, 0, 0] |
| 27 | +[0, 0, 0, 2] |
| 28 | +
|
| 29 | +Grid Recurrence for HZPs (X == -1) |
| 30 | +[1, 0, 0, 0] -> [1, 0, 0, 0] |
| 31 | +[0, 0, 0, 0] [0, 0, 0, 2] 2 ways for acyclic paths of len 11 |
| 32 | +[0, 0, 0, 2] [0, 0, 0, X] |
| 33 | + + |
| 34 | + [1, 0, 0, 0] |
| 35 | + [0, 0, 0, 0] 2 ways for acyclic paths of len 11 |
| 36 | + [0, 0, 2, X] |
| 37 | +
|
| 38 | +Does this strategy evaluate paths it doesn't need to? Doesn't seem different |
| 39 | +from just trying a quadratic DFS from 1. |
| 40 | +- Don't need to repeatedly try to find (y,x,k), i.e. count of valid k-len paths to grid[y,x] |
| 41 | +- Do we need to try (y,x,k) and (y,x,n) if n != k? some paths may take (y,x) as the nth node, others as the kth node. |
| 42 | +
|
| 43 | +Trying smaller example: |
| 44 | +This has 1 way to make acyclic path of len 5. |
| 45 | +[1, 0, 0] |
| 46 | +[0, 0, 2] |
| 47 | +
|
| 48 | +
|
| 49 | +Grid Recurrence for HZPs (X == -1) |
| 50 | +[1, 0, 0] -> [1, 0, 2] 1 path |
| 51 | +[0, 0, 2] [0, 0, x] |
| 52 | + + |
| 53 | + [1, 0, 0] |
| 54 | + [0, 2, X] 0 paths |
| 55 | +
|
| 56 | +[1, 2, X] -> .... (etc) |
| 57 | +[0, 0, X] |
| 58 | +
|
| 59 | +""" |
| 60 | +from typing import List |
| 61 | + |
| 62 | + |
| 63 | +def is_valid(grid, y, x): |
| 64 | + return (0 <= y < len(grid)) and (0 <= x < len(grid[0])) |
| 65 | + |
| 66 | + |
| 67 | +def get_neighbors(grid, y, x): |
| 68 | + neighbors = [] |
| 69 | + for dy, dx in [(-1, 0), (1, 0), (0, -1), (0, 1)]: |
| 70 | + neighbors.append((y+dy, x+dx)) if is_valid(grid, y+dy, x+dx) else None |
| 71 | + return neighbors |
| 72 | + |
| 73 | + |
| 74 | +def dfs(grid, y, x, path_len, visited): |
| 75 | + cell = grid[y][x] |
| 76 | + if (y, x) in visited or cell == -1: |
| 77 | + return 0 |
| 78 | + if cell == 2: |
| 79 | + return 1 if path_len == 0 else 0 |
| 80 | + |
| 81 | + paths = 0 |
| 82 | + visited.add((y, x)) |
| 83 | + for y_n, x_n in get_neighbors(grid, y, x): |
| 84 | + paths += dfs(grid, y_n, x_n, path_len-1, visited) |
| 85 | + visited.remove((y, x)) |
| 86 | + return paths |
| 87 | + |
| 88 | + |
| 89 | +class Solution: |
| 90 | + def uniquePathsIII(self, grid: List[List[int]]) -> int: |
| 91 | + start_y = start_x = 0 |
| 92 | + required_cells = len(grid) * len(grid[0]) |
| 93 | + for y, row in enumerate(grid): |
| 94 | + for x, cell in enumerate(row): |
| 95 | + if cell == -1: |
| 96 | + required_cells -= 1 |
| 97 | + elif cell == 1: |
| 98 | + start_y, start_x = y, x |
| 99 | + required_cells -= 1 |
| 100 | + return dfs(grid, start_y, start_x, required_cells, set()) |
| 101 | + |
| 102 | + |
| 103 | +s = Solution() |
| 104 | +cases = [ |
| 105 | + ([ |
| 106 | + [1, 0, 0, 0], |
| 107 | + [0, 0, 0, 0], |
| 108 | + [0, 0, 2, -1]], 2), |
| 109 | + ([ |
| 110 | + [1, 0, 0, 0], |
| 111 | + [0, 0, 0, 0], |
| 112 | + [0, 0, 0, 2]], 4), |
| 113 | + ([ |
| 114 | + [0, 1], |
| 115 | + [2, 0]], 0), |
| 116 | +] |
| 117 | +for grid, expected in cases: |
| 118 | + actual = s.uniquePathsIII(grid) |
| 119 | + try: |
| 120 | + assert actual == expected |
| 121 | + except AssertionError: |
| 122 | + print("=="*10) |
| 123 | + for row in grid: |
| 124 | + print(row) |
| 125 | + print(f"{expected} != {actual}") |
| 126 | + raise |
0 commit comments