diff --git a/1293.Shortest-Path-in-a-Grid-with-Obstacles-Elimination/memo.md b/1293.Shortest-Path-in-a-Grid-with-Obstacles-Elimination/memo.md new file mode 100644 index 0000000..5e3209e --- /dev/null +++ b/1293.Shortest-Path-in-a-Grid-with-Obstacles-Elimination/memo.md @@ -0,0 +1,58 @@ +# 1293. Shortest Path in a Grid with Obstacles Elimination + +## step1 +24mほどかかった。 + +BFSを思いついた。 + +visited を setにするとTLEするので、remainingの最大値を保持しておく。 + +計算量はどちらも O(mnk) で実行時間に直すと 40**4 / 10**7 = 0.256s + +DPを少し考えたが無理ではないか?下右だけの遷移ならば一方通行なので可能だが。 + +## 他の人の解法 + + +```python +class Solution: + def shortestPath(self, grid: list[list[int]], k: int) -> int: + + m, n = len(grid), len(grid[0]) + # [1] this check significantly improves runtime, i.e., + # we can use path (0,0) -> (0,n-1) -> (m-1,n-1) + if k >= m + n - 2: return m + n - 2 + + # [2] we use deque to store and update a BFS state that is + # (x, y, obstacles we can destroy, steps done so far) + dq = deque([(0, 0, k, 0)]) + # [3] we also keep track of visited cells + seen = set() + + while dq: + i, j, k, s = dq.popleft() + # [4] successfully reached lower right corner + if (i,j) == (m-1,n-1) : return s + + # [5] scan all possible directions + for ii, jj in [(i+1,j),(i-1,j),(i,j+1),(i,j-1)]: + # [6] check boundaries and obstacles + if 0 <= ii < m and 0 <= jj < n and k >= grid[ii][jj]: + # [7] make (and remember) a step + step = (ii, jj, k-grid[ii][jj], s+1) + if step[0:3] not in seen: + seen.add(step[0:3]) + dq.append(step) + + # [8] failed to reach lower right corner + return -1 +``` + +set() を使っているがAC。`if k >= m + n - 2: return m + n - 2`が効いている + +自分のに入れても 104ms -> 3ms と高速化した + +--- + +他の解法はなさそうだが、LLMによるとA-starが使えるかもしれない + diff --git a/1293.Shortest-Path-in-a-Grid-with-Obstacles-Elimination/step1.py b/1293.Shortest-Path-in-a-Grid-with-Obstacles-Elimination/step1.py new file mode 100644 index 0000000..cbb914d --- /dev/null +++ b/1293.Shortest-Path-in-a-Grid-with-Obstacles-Elimination/step1.py @@ -0,0 +1,44 @@ +EMPTY = 0 +OBSTACLE = 1 + +class Solution: + + def shortestPath(self, grid: List[List[int]], k: int) -> int: + if not grid or not grid[0]: + return -1 + + num_rows = len(grid) + num_cols = len(grid[0]) + + max_remaining_k = [[-1] * num_cols for _ in range(num_rows)] + max_remaining_k[0][0] = k + + frontier = [(0, 0, k)] + steps = 0 + + while frontier: + next_frontier = [] + for row, col, remaining_k in frontier: + if row == num_rows - 1 and col == num_cols - 1: + return steps + + for next_row, next_col in ((row + 1, col), (row - 1, col), (row, col + 1), (row, col - 1)): + if not (0 <= next_row < num_rows and 0 <= next_col < num_cols): + continue + + is_obstacle = (grid[next_row][next_col] == OBSTACLE) + next_remaining_k = remaining_k - (1 if is_obstacle else 0) + + if next_remaining_k < 0: + continue + + if max_remaining_k[next_row][next_col] >= next_remaining_k: + continue + + max_remaining_k[next_row][next_col] = next_remaining_k + next_frontier.append((next_row, next_col, next_remaining_k)) + + frontier = next_frontier + steps += 1 + + return -1 diff --git a/1293.Shortest-Path-in-a-Grid-with-Obstacles-Elimination/step2.py b/1293.Shortest-Path-in-a-Grid-with-Obstacles-Elimination/step2.py new file mode 100644 index 0000000..b8632c8 --- /dev/null +++ b/1293.Shortest-Path-in-a-Grid-with-Obstacles-Elimination/step2.py @@ -0,0 +1,47 @@ +EMPTY = 0 +OBSTACLE = 1 + +class Solution: + + def shortestPath(self, grid: List[List[int]], k: int) -> int: + if not grid or not grid[0]: + return -1 + + num_rows = len(grid) + num_cols = len(grid[0]) + + if k >= num_rows + num_cols - 2: + return num_rows + num_cols - 2 + + max_remaining_k = [[-1] * num_cols for _ in range(num_rows)] + max_remaining_k[0][0] = k + + frontier = [(0, 0, k)] + steps = 0 + + while frontier: + next_frontier = [] + for row, col, remaining_k in frontier: + if row == num_rows - 1 and col == num_cols - 1: + return steps + + for next_row, next_col in ((row + 1, col), (row - 1, col), (row, col + 1), (row, col - 1)): + if not (0 <= next_row < num_rows and 0 <= next_col < num_cols): + continue + + is_obstacle = (grid[next_row][next_col] == OBSTACLE) + next_remaining_k = remaining_k - (1 if is_obstacle else 0) + + if next_remaining_k < 0: + continue + + if max_remaining_k[next_row][next_col] >= next_remaining_k: + continue + + max_remaining_k[next_row][next_col] = next_remaining_k + next_frontier.append((next_row, next_col, next_remaining_k)) + + frontier = next_frontier + steps += 1 + + return -1 diff --git a/1293.Shortest-Path-in-a-Grid-with-Obstacles-Elimination/step2_deque.py b/1293.Shortest-Path-in-a-Grid-with-Obstacles-Elimination/step2_deque.py new file mode 100644 index 0000000..447a2ab --- /dev/null +++ b/1293.Shortest-Path-in-a-Grid-with-Obstacles-Elimination/step2_deque.py @@ -0,0 +1,43 @@ +import collections + +EMPTY = 0 +OBSTACLE = 1 + +class Solution: + + def shortestPath(self, grid: List[List[int]], k: int) -> int: + if not grid or not grid[0]: + return -1 + + num_rows = len(grid) + num_cols = len(grid[0]) + + if k >= num_rows + num_cols - 2: + return num_rows + num_cols - 2 + + max_remaining_k = [[-1] * num_cols for _ in range(num_rows)] + max_remaining_k[0][0] = k + + dq = collections.deque([(0, 0, k, 0)]) + + while dq: + row, col, remaining_k, steps = dq.popleft() + if row == num_rows - 1 and col == num_cols - 1: + return steps + + for next_row, next_col in ((row + 1, col), (row - 1, col), (row, col + 1), (row, col - 1)): + if not (0 <= next_row < num_rows and 0 <= next_col < num_cols): + continue + + next_remaining_k = remaining_k - 1 if (grid[next_row][next_col] == OBSTACLE) else remaining_k + + if next_remaining_k < 0: + continue + + if max_remaining_k[next_row][next_col] >= next_remaining_k: + continue + + max_remaining_k[next_row][next_col] = next_remaining_k + dq.append((next_row, next_col, next_remaining_k, steps + 1)) + + return -1