[LeetCode] #54. Spiral Matrix

題目

Given an m x n matrix, return all elements of the matrix in spiral order.

直覺解

構想:

dictionary.target 紀錄接下來要印哪一個 row/ cole,用 dictionary.star 和 ictionary.end 紀錄是從哪裡印到哪裡。

如果 cell 位於 row 和 col 的交叉點,則把該 cell 歸類為 row。以 matrix = [[1, 2, 3], [4, 5, 6], [7, 8, 9]] 為例,cell 的歸類順序是:

  • 上 row: 1, 2, 3

  • 右 col: 6

  • 下 row: 9, 8, 7

  • 左 col: 4

  • 上 row: 5

python 3 實作(失敗):

class Solution: 
    ans = []
    def spiralOrder(self, matrix: List[List[int]]) -> List[int]:
        self.ans = []
        if matrix is None:
            return self.ans

        row = {'target': 0, 'star': 0, 'end': len(matrix[0]), 'reverse': False}
        col = {
            'target': len(matrix[0]) - 1,
            'star': row['target'] + 1,
            'end': len(matrix) - 1,
            'reverse': False
        }

        while row['star'] < row['end'] and col['star'] < col['end']:
            if row['star'] < row['end']:
                self.matrix_row(row, col, matrix)
            if col['star'] < col['end']:
                self.matrix_col(row, col, matrix)
        else:
            if row['star'] < row['end']:
                self.matrix_row(row, col, matrix)
            elif col['star'] < col['end']:
                self.matrix_col(row, col, matrix)
        return self.ans

    def matrix_row(self, row, col, matrix):
        if row['reverse'] is False:
            for num in matrix[row['target']][row['star']:row['end']]:
                self.ans.append(num)
            col['target'] = row['end'] - 1

        else:
            for num in reversed(matrix[row['target']][row['star']:row['end']]):
                self.ans.append(num)

            col['target'] = row['star']
            row['star'] += 1
            row['end'] -= 1
        row['reverse'] = not row['reverse']

    def matrix_col(self, row, col, matrix):
        if col['reverse'] is False:
            for i in range(col['star'], col['end']):
                self.ans.append(matrix[i][col['target']])
            row['target'] = col['end']

        else:
            for i in reversed(range(col['star'], col['end'])):
                self.ans.append(matrix[i][col['target']])
            row['target'] = col['star']
            col['star'] += 1
            col['end'] -= 1
        col['reverse'] = not col['reverse']

Submission Detail

Wrong Answer

Input: [[3],[2]]

Output: [3]

Expected: [3,2]

失敗原因

因為「如果 cell 位於 row 和 col 的交叉點,則把該 cell 歸類為 row。」的設計,造成補東牆破西牆的問題。

原本的 while 迴圈長得比較清爽:

        while row['star'] < row['end'] or col['star'] < col['end']: 
            if row['star'] < row['end']:
                self.matrix_row(row, col, matrix)
            if col['star'] < col['end']:
                self.matrix_col(row, col, matrix)

但原本的 while 迴圈在 Input: [[1, 2, 3], [4, 5, 6], [7, 8, 9]]時,在 row['reverse'] 是 False 時已經窮盡了 matrix 中的所有 cell,但因為 row 的 star 和 end 只會在 row['reverse'] 是 True 時更新,導致重複把 5 放進去 ans 裡。為了避免這個問題,才把 while 迴圈改成現在的樣子。

但現在的 while 迴圈在 Input: [[3],[2]] 時,因為歸類 cell 的方式,導致沒有 col,而只有兩個 row,所以我需要執行兩次 matrix_row 函式,才能完成題目指定的結果。但我的程式碼只允許執行一次 matrix_row 函式(因為沒辦法滿足 col['star'] < col['end'] 的條件)。

構想:

更改 cell 的歸類方式。以 matrix = [[1, 2, 3], [4, 5, 6], [7, 8, 9]] 為例,cell 的 歸類順序是:

  • 上 row: 1, 2, 3

  • 右 col: 6, 9

  • 下 row: 8, 7

  • 左 col: 4

  • 上 row: 5

python 3 實作(失敗):

class Solution: 
    ans = []

    def spiralOrder(self, matrix: List[List[int]]) -> List[int]:
        self.ans = []
        if matrix is None:
            return self.ans

        row = {'target': 0, 'star': 0, 'end': len(matrix[0]), 'reverse': False}
        col = {
            'target': len(matrix[0]) - 1,
            'star': row['target'] + 1,
            'end': len(matrix),
            'reverse': False
        }

        while row['star'] < row['end'] and col['star'] < col['end']:
            if row['star'] < row['end']:
                self.matrix_row(row, col, matrix)
            if col['star'] < col['end']:
                self.matrix_col(row, col, matrix)
        return self.ans

    def matrix_row(self, row, col, matrix):
        if row['reverse'] is False:
            for num in matrix[row['target']][row['star']:row['end']]:
                self.ans.append(num)
            col['target'] = row['end'] - 1
            col['star'] = row['target'] + 1

        else:
            for num in reversed(matrix[row['target']][row['star']:row['end']]):
                self.ans.append(num)

            col['target'] = row['star']
            col['end'] = row['target']

        row['reverse'] = not row['reverse']

    def matrix_col(self, row, col, matrix):
        if col['reverse'] is False:
            for i in range(col['star'], col['end']):
                self.ans.append(matrix[i][col['target']])
            row['target'] = col['end'] - 1
            row['end'] = col['target']

        else:
            for i in reversed(range(col['star'], col['end'])):
                self.ans.append(matrix[i][col['target']])
            row['target'] = col['star']
            row['star'] = col['target'] + 1

        col['reverse'] = not col['reverse']

Submission Detail

Wrong Answer

Input: [[1]]

Output: []

Expected: [1]

失敗原因

len(matrix) 是 1 的時候,進不去 while 迴圈。

構想:

len(matrix) 是 1 的時候,當作特例處理。

python 3 實作:

class Solution: 
    ans = []

    def spiralOrder(self, matrix: List[List[int]]) -> List[int]:
        self.ans = []
        if matrix is None:
            return self.ans
        if len(matrix) == 1:
            return matrix[0][:]

        row = {'target': 0, 'star': 0, 'end': len(matrix[0]), 'reverse': False}
        col = {
            'target': len(matrix[0]) - 1,
            'star': row['target'] + 1,
            'end': len(matrix),
            'reverse': False
        }

        while row['star'] < row['end'] and col['star'] < col['end']:
            if row['star'] < row['end']:
                self.matrix_row(row, col, matrix)
            if col['star'] < col['end']:
                self.matrix_col(row, col, matrix)
        return self.ans

    def matrix_row(self, row, col, matrix):
        if row['reverse'] is False:
            for num in matrix[row['target']][row['star']:row['end']]:
                self.ans.append(num)
            col['target'] = row['end'] - 1
            col['star'] = row['target'] + 1

        else:
            for num in reversed(matrix[row['target']][row['star']:row['end']]):
                self.ans.append(num)

            col['target'] = row['star']
            col['end'] = row['target']

        row['reverse'] = not row['reverse']

    def matrix_col(self, row, col, matrix):
        if col['reverse'] is False:
            for i in range(col['star'], col['end']):
                self.ans.append(matrix[i][col['target']])
            row['target'] = col['end'] - 1
            row['end'] = col['target']

        else:
            for i in reversed(range(col['star'], col['end'])):
                self.ans.append(matrix[i][col['target']])
            row['target'] = col['star']
            row['star'] = col['target'] + 1

        col['reverse'] = not col['reverse']

效能:

Runtime: 20 ms, faster than 99.19% of Python3 online submissions for Spiral Matrix.
Memory Usage: 14.3 MB, less than 27.63% of Python3 online submissions for Spiral Matrix.

感想:這個程式碼,讀起來真是太眼花撩亂了⋯⋯

研究別人的解

來源:Python Easy to Understand beats 98 %

研究重點:怎麼寫得更易讀一點。

別人的 python 3 實作:

class Solution: 
    def spiralOrder(self, matrix: List[List[int]]) -> List[int]:
        m = len(matrix)
        n = len(matrix[0])
        left = 0
        right = n-1
        down = m-1
        top = 0
        direction = 0
        ans = []
        while(left<=right and down>=top):

            if direction ==0:
                # go right
                for i in range(left , right+1):
                    ans.append(matrix[top][i])
                top+=1
            elif direction==1:
                #go down
                for j in range(top , down+1):
                    ans.append(matrix[j][right])
                right-=1
            elif direction ==2:
                #go left
                for j in range(right , left-1,-1):
                    ans.append(matrix[down][j])
                down-=1
            elif direction==3:
                #go up
                for j in range(down , top-1 , -1):
                    ans.append(matrix[j][left])
                left+=1

            direction= (direction +1)%4

        return ans

效能:

Runtime: 28 ms, faster than 83.67% of Python3 online submissions for Spiral Matrix.
Memory Usage: 14.4 MB, less than 27.56% of Python3 online submissions for Spiral Matrix.

研究感想

direction 的用法好妙!直接用變數存上下左右的範圍,而不是包在 dictionary 裡面,value 取用起來乾淨俐落很多。依照我的寫法,還要特地存一個 target 用來標示要印哪一個 row/ col,但是他只要用上下左右的範圍,就可以直接知道輪到印哪一個 row/ col 了。原來 range 這樣使用就可以把數字由大往小的方向迭代。

他的「上」初始值是 0。我的「上」初始值是 row['target'] + 1,導致 matrix 只有一行的時候就進不去我的 while 迴圈,而需要把只有一行的 matrix 當作特例處理。

我的 while 迴圈裡的 if 其實可以刪掉。

小幅度優化我的 python 實作:為了方便對照,刪掉的地方姑且用 # 註解掉

class Solution: 
    ans = []

    def spiralOrder(self, matrix: List[List[int]]) -> List[int]:
        self.ans = []
        if matrix is None:
            return self.ans
        # if len(matrix) == 1:
            # return matrix[0][:]

        row = {'target': 0, 'star': 0, 'end': len(matrix[0]), 'reverse': False}
        col = {
            'target': len(matrix[0]) - 1,
            'star': 0,
            'end': len(matrix),
            'reverse': False
        }

        while row['star'] < row['end'] and col['star'] < col['end']:
            # if row['star'] < row['end']:
                self.matrix_row(row, col, matrix)
            # if col['star'] < col['end']:
                self.matrix_col(row, col, matrix)
        return self.ans

    def matrix_row(self, row, col, matrix):
        if row['reverse'] is False:
            for num in matrix[row['target']][row['star']:row['end']]:
                self.ans.append(num)
            col['target'] = row['end'] - 1
            col['star'] = row['target'] + 1

        else:
            for num in reversed(matrix[row['target']][row['star']:row['end']]):
                self.ans.append(num)

            col['target'] = row['star']
            col['end'] = row['target']

        row['reverse'] = not row['reverse']

    def matrix_col(self, row, col, matrix):
        if col['reverse'] is False:
            for i in range(col['star'], col['end']):
                self.ans.append(matrix[i][col['target']])
            row['target'] = col['end'] - 1
            row['end'] = col['target']

        else:
            for i in reversed(range(col['star'], col['end'])):
                self.ans.append(matrix[i][col['target']])
            row['target'] = col['star']
            row['star'] = col['target'] + 1

        col['reverse'] = not col['reverse']

效能:

第一次: Runtime: 24 ms, faster than 95.43% of Python3 online submissions for Spiral Matrix.
Memory Usage: 14 MB, less than 95.18% of Python3 online submissions for Spiral Matrix.

第二次: Runtime: 28 ms, faster than 83.67% of Python3 online submissions for Spiral Matrix.
Memory Usage: 14.4 MB, less than 27.56% of Python3 online submissions for Spiral Matrix.

Comments

Popular posts from this blog

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

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

shop_platform - sqlalchemy.exc.TimeoutError