[LeetCode] #1091. Shortest Path in Binary Matrix

題目

Given an n x n binary matrix grid, return the length of the shortest clear path in the matrix. If there is no clear path, return -1.

A clear path in a binary matrix is a path from the top-left cell (i.e., (0, 0)) to the bottom-right cell (i.e., (n - 1, n - 1)) such that:

  • All the visited cells of the path are 0.
  • All the adjacent cells of the path are 8-directionally connected (i.e., they are different and they share an edge or a corner).

The length of a clear path is the number of visited cells of this path.

直覺解

構想:

用遞迴。從 grid[0][0] 的點出發,按照「右下、下、右、左下、右上」的優先順序,依序嘗試下一個點要往哪個方向走,直到走到 grid[n-1][n-1] 或沒路了為止。

python 3 實作(失敗):

def shortestPathBinaryMatrix(self, grid: List[List[int]]) -> int: 
    ans = -1
    n = len(grid)
    if grid[n-1][n-1] != 0:
        return ans

    def findPath(current, ans):
        i, j = current

        if i >= n or j >=n:
            return -1

        if grid[i][j] != 0:
            return -1
        ans += 1

        if i == n-1 and j == n-1:
            return ans

        result = findPath((i+1, j+1), ans)
        if result > 0:
            return result

        result = findPath((i+1, j), ans)
        if result > 0:
            return result

        result = findPath((i, j+1), ans)
        if result > 0:
            return result

        result = findPath((i+1, j-1), ans)
        if result > 0:
            return result

        result = findPath((i-1, j+1), ans)
        if result > 0:
            return result
        return -1

    current = (0, 0)
    return findPath(current, 0)

執行結果:

Runtime Error Message: RecursionError: maximum recursion depth exceeded in comparison

過程中犯過的錯誤

  • 忘記設定,如果到達 grid[n-1][n-1] 的話就可以結束。

構想:

改良的地方:放棄遞迴,用 stack。

自己的 python 3 實作(失敗):

def shortestPathBinaryMatrix(self, grid: List[List[int]]) -> int: 
    n = len(grid)
    if grid[n-1][n-1] != 0:
        return -1

    stack = [(0, 0, 0)]
    while stack:
        i, j, count = stack.pop()

        if i >= n or j >= n:
          continue
        if i < 0 or j < 0:
          continue
        if grid[i][j] != 0:
          continue

        count += 1
        if i == n-1 and j == n-1:
return count

        stack.extend([(i-1, j+1, count), (i+1, j-1, count), (i, j+1, count), (i+1, j, count) ,(i+1, j+1, count)])

    return -1

執行結果:

Time Limit Exceeded

Last executed input: [[0,1,1,0,0,0],[0,1,0,1,1,0],[0,1,1,0,1,0],[0,0,0,1,1,0],[1,1,1,1,1,0],[1,1,1,1,1,0]]

錯誤發生原因:

  • 因為沒有限制「不能走回頭路」,結果在 grid[3][2] 和 grid[2][3] 之間徘徊,造成無窮迴圈。
  • 只考慮了五個方向,但其實下一步能走的方向總共有八個。

構想:

改良的地方:按照「右下、下、右、左下、右上、左、上、右上」的優先順序,依序嘗試。走過的點,就把值從 0 改成 2,以免走回頭路。

自己的 python 3 實作 (失敗):

def shortestPathBinaryMatrix(self, grid: List[List[int]]) -> int: 
    n = len(grid)
    if grid[n-1][n-1] != 0:
        return -1

    stack = [(0, 0, 0)]
    while stack:
        i, j, count = stack.pop()

        if i >= n or j >= n:
            continue
        if i < 0 or j < 0:
            continue
        if grid[i][j] != 0:
            continue

        grid[i][j] = 2
        count += 1
        if i == n-1 and j == n-1:
            return count

        stack.extend([(i-1, j-1, count), (i-1, j, count), (i, j-1, count), (i-1, j+1, count), (i+1, j-1, count), (i, j+1, count), (i+1, j, count) ,(i+1, j+1, count)])

    return -1

執行結果:

Wrong Answer

Input: [[0,1,0,0,0,0],[0,1,0,1,1,0],[0,1,1,0,1,0],[0,0,0,0,1,0],[1,1,1,1,1,0],[1,1,1,1,1,0]]

Output: 15

Expected: 14

錯誤發生原因:

  • 在 grid[3][2] 這個點時,最短路徑應該是往右上走,但依照我的優先順序,會變成先往右再往上,於是就多了一步。

構想:

改良的地方:優先順序改成「右下、左下、右上、下、右、左上、左、上」

自己的 python 3 實作 (失敗):

def shortestPathBinaryMatrix(self, grid: List[List[int]]) -> int: 
    n = len(grid)
    if grid[n-1][n-1] != 0:
        return -1

    stack = [(0, 0, 0)]
    while stack:
        i, j, count = stack.pop()

        if i >= n or j >= n:
            continue
        if i > 0 or j > 0:
            continue
        if grid[i][j] != 0:
            continue


        grid[i][j] = 2
                count += 1
        if i == n-1 and j == n-1:
            return count

        stack.extend([(i-1, j, count), (i, j-1, count), (i-1, j-1, count), (i, j+1, count), (i+1, j, count), (i-1, j+1, count), (i+1, j-1, count), (i+1, j+1, count)])

    return -1

執行結果:

Wrong Answer

Input: [[0,0,0],[0,1,0],[0,0,0]]

Output: 5

Expected: 4

錯誤發生原因:

  • 在 grid[2][1] 這個點時,最短路徑應該是往右走,但依照我的優先順序,會變成先往右上再往下,於是就多了一步。
  • 感覺起來,除了設定優先順序以外,還要設定別的機制,才能每次都找到最短路徑。

研究別人的解

來源:Python BFS

別人的 python 3 實作:

def shortestPathBinaryMatrix(self, grid: List[List[int]]) -> int: 
    N = len(grid)
    dis = [[-1] * N for _ in range(N)]
    dis[0][0] = 1

    q = [(0, 0)] if grid[0][0] == 0 else []
    while q:
        r, c = q.pop(0)
        for i, j in [(r+1, c+1), (r+1, c), (r, c+1), (r-1, c+1), (r+1, c-1), (r-1, c), (r, c-1), (r-1, c-1)]:
            if 0 <= i < N and 0 <= j < N and dis[i][j] == -1 and grid[i][j] == 0:
                dis[i][j] = dis[r][c] + 1
                q.append((i, j))
                if i == j == N-1:
                    break


    return dis[-1][-1]

效能:

Runtime: 612 ms, faster than 84.87% of Python3 online submissions for Shortest Path in Binary Matrix.
Memory Usage: 14.7 MB, less than 63.06% of Python3 online submissions for Shortest Path in Binary Matrix.

研究別人的實作時,加上的 print

def shortestPathBinaryMatrix(self, grid: List[List[int]]) -> int: 
    N = len(grid)
    dis = [[-1] * N for _ in range(N)]
    dis[0][0] = 1

    q = [(0, 0)] if grid[0][0] == 0 else []
    while q:
        print('-------')
        print(q)
        r, c = q.pop(0)
        for i, j in [(r+1, c+1), (r+1, c), (r, c+1), (r-1, c+1), (r+1, c-1), (r-1, c), (r, c-1), (r-1, c-1)]:
            print(i, j)
            if 0 <= i < N and 0 <= j < N and dis[i][j] == -1 and grid[i][j] == 0:
                print('filtered')
                dis[i][j] = dis[r][c] + 1
                q.append((i, j))
                if i == j == N-1:
                    break

    return dis[-1][-1]

研究感想:

我的寫法,會在 stack 裡面塞一堆「下一步要往哪個方向」,而且找到方向之後,不會把沒有被選到的方向清空,所以 stack 裡面有很多無用的資訊。但是這個人的寫法就沒有這個問題。

避免走回頭路,我的寫法,是把走過的 cell 值改成 2。他則是做了一個每個 cell 都是 -1 的新 grid,走過的 cell 就改成「這條路的第幾步」(例如,dis[0][0] 固定都是第一步,而最後的回傳值則是 dis[-1][-1]),所以值是 -1 的話代表沒有走過。

他的優先順序是設定成「右下、下、右、右上、左下、上、左、左上」。我如果用這個順序的話,在 [[0,1,0,0,0,0],[0,1,0,1,1,0],[0,1,1,0,1,0],[0,0,0,0,1,0],[1,1,1,1,1,0],[1,1,1,1,1,0]] 這一題會多走一步。以此題目為例,站在 grid[3][2] 時,我的寫法,找到右邊是可以走的時候,就會直接往那裡走了;但是他的寫法,找到右邊是可以走的時候,他還會繼續看右上能不能走、左下能不能走、⋯⋯直到迭代完所有的可能性,等到下一圈 while,因為已經走過的就不能再走了,如果不是最短的路,就沒有路可以走了,這樣就能篩選出最短的路。這個設計真是太精妙了!!!請收下我的膝蓋!!!!怎麼可以這麼強,我好興奮啊啊啊啊!!!!

不過,走到 grid[N-1][N-1] 這格時,其實可以 return dis[-1][-1] 而不是 break,這樣就不用多跑一次 while 迴圈。

拯救我的直覺解

構想:

改良的地方:不要用 stack,改成 queue。從尾巴拿,和從開頭拿,就足以造成失敗或成功的差別,這個領域真是太令人驚嘆了!

python 3 實作(成功):

def shortestPathBinaryMatrix(self, grid: List[List[int]]) -> int: 
    n = len(grid)
    if grid[n-1][n-1] != 0:
        return -1

    queue = [(0, 0, 0)]
    while queue:
        i, j, count = queue.pop(0)

        if i >= n or j >= n:
           continue
        if i < 0 or j < 0:
           continue
        if grid[i][j] != 0:
           continue

        grid[i][j] = 2
        count += 1
        if i == n-1 and j == n-1:
           return count

        queue.extend([(i-1, j-1, count), (i, j-1, count), (i-1, j, count), (i+1, j-1, count), (i-1, j+1, count), (i, j+1, count), (i+1, j, count) ,(i+1, j+1, count)])

    return -1

效能:

Runtime: 756 ms, faster than 40.96% of Python3 online submissions for Shortest Path in Binary Matrix.
Memory Usage: 14.7 MB, less than 76.74% of Python3 online submissions for Shortest Path in Binary Matrix.

Comments

Popular posts from this blog

Alpha Camp 全端開發課程學習心得

在 javascript 用 regular expression 為金額加上千位數分隔符號

shop_platform - sqlalchemy.exc.TimeoutError