Spiral Matrix
The problem description can be found in LeetCode.
Approach
For this problem, it is enough to follow this pattern for the directions:
- Right.
- Down.
- Left.
- Up.
Then, we can repeat the process until the are elements to check.
We just need to be careful and avoid to visit an element more than once. We could use a Set to achieve this.
from enum import Enum
from typing import List, Tuple
class Direction(Enum):
UP = 0
DOWN = 1
LEFT = 2
RIGHT = 3
class Solution:
def spiralOrder(self, matrix: List[List[int]]) -> List[int]:
rows, cols = len(matrix), len(matrix[0])
n = rows * cols
pos = Direction.RIGHT
row = col = 0
ans: List[int] = []
visited = {(0, 0)}
stack = [(0, 0)]
while len(ans) != n:
row, col = stack.pop()
ans.append(matrix[row][col])
new_row, new_col = self.get_next_position(row, col, pos)
if (
not self.is_valid_position(new_row, new_col, rows, cols)
or (new_row, new_col) in visited
):
pos = self.change_position(pos)
new_row, new_col = self.get_next_position(row, col, pos)
stack.append((new_row, new_col))
visited.add((new_row, new_col))
return ans
def change_position(self, pos: Direction) -> Direction:
if pos == Direction.UP:
return Direction.RIGHT
if pos == Direction.DOWN:
return Direction.LEFT
if pos == Direction.LEFT:
return Direction.UP
if pos == Direction.RIGHT:
return Direction.DOWN
assert False, f"unknown position `{pos}`"
def get_next_position(self, row: int, col: int, pos: Direction) -> Tuple[int, int]:
if pos == Direction.UP:
return row - 1, col
if pos == Direction.DOWN:
return row + 1, col
if pos == Direction.LEFT:
return row, col - 1
if pos == Direction.RIGHT:
return row, col + 1
assert False, f"unknown position `{pos}`"
def is_valid_position(self, row: int, col: int, rows: int, cols: int) -> bool:
return 0 <= row < rows and 0 <= col < cols
This solution would work, but it requires extra space for both the next element to pick and the visited coordinates.
However, there is a better approach that won't use any extra space. Let's derive some examples:
a b c d
e f g h
i j k l
This would would require:
|cols|
steps right.|rows| - 1
steps down.|cols| - 1
steps left.|rows| - 2
steps up.|cols| - 2
steps right.
Moreover, we can notice that between (right, down) and (left, up) only the directions are different. So, if we group these four operations into two groups, we would end up by just having horizontal (left and right) and vertical (up and down) movements.
Solution
from typing import List
class Solution:
def spiralOrder(self, matrix: List[List[int]]) -> List[int]:
rows, cols = len(matrix), len(matrix[0])
n = rows * cols
row, col = 0, -1
direction = 1
ans: List[int] = []
while len(ans) != n:
# horizontal
for _ in range(cols):
col += direction
ans.append(matrix[row][col])
rows -= 1
# vertical
for _ in range(rows):
row += direction
ans.append(matrix[row][col])
cols -= 1
direction *= -1
return ans
Complexity
- Time:
- Space:
Where and are the number for rows and columns of the matrix, respectively.