Spiral Matrix
MedPrompt
Given an m x n matrix, return all elements of the matrix in spiral order.
Example 1:
Input: matrix = [[1,2,3],[4,5,6],[7,8,9]]
Output: [1,2,3,6,9,8,7,4,5]Example 2:
Input: matrix = [[1,2,3,4],[5,6,7,8],[9,10,11,12]]
Output: [1,2,3,4,8,12,11,10,9,5,6,7]
Constraints:
m == matrix.lengthn == matrix[i].length1 <= m, n <= 10-100 <= matrix[i][j] <= 100
Approaches
2 approaches with complexity analysis and trade-offs.
This approach simulates the spiral traversal by keeping track of the current position (row, column) and the current direction of movement (right, down, left, up). To prevent visiting the same cell multiple times, we use an auxiliary boolean matrix of the same dimensions, visited, to mark cells that have already been added to the result.
Algorithm
- Initialize an empty list
resultto store the spiral order elements. - If the input matrix is empty, return the empty list.
- Get the dimensions of the matrix,
rowsandcols. - Create a
visitedboolean matrix of sizerows x colsand initialize all its elements tofalse. - Define the four directions of movement using two arrays:
dr = {0, 1, 0, -1}for row changes anddc = {1, 0, -1, 0}for column changes, corresponding to right, down, left, and up. - Initialize the starting position
r = 0,c = 0, and the initial direction indexdi = 0. - Iterate
rows * colstimes, as we need to visit every element once.- Add the element
matrix[r][c]to theresultlist. - Mark the current cell as visited:
visited[r][c] = true. - Calculate the coordinates of the next cell in the current direction:
nextR = r + dr[di],nextC = c + dc[di]. - Check if the next cell is out of bounds or has already been visited.
- If the next cell is invalid, change the direction by updating the direction index:
di = (di + 1) % 4. - Update the current position
(r, c)for the next iteration using the (possibly new) direction.
- Add the element
- Return the
resultlist.
Walkthrough
The core idea is to walk the spiral path step by step. We start at the top-left corner (0, 0) and move right. We continue in one direction until we either hit the matrix boundary or a cell that we have already visited. When that happens, we turn right (e.g., from moving right to moving down) and continue. We repeat this process of moving and turning until we have visited all the cells in the matrix. A visited array is essential to keep track of our path and prevent infinite loops or incorrect traversals.
class Solution { public List<Integer> spiralOrder(int[][] matrix) { List<Integer> result = new ArrayList<>(); if (matrix == null || matrix.length == 0) { return result; } int rows = matrix.length; int cols = matrix[0].length; boolean[][] visited = new boolean[rows][cols]; int[] dr = {0, 1, 0, -1}; // right, down, left, up int[] dc = {1, 0, -1, 0}; int r = 0, c = 0, di = 0; for (int i = 0; i < rows * cols; i++) { result.add(matrix[r][c]); visited[r][c] = true; int nextR = r + dr[di]; int nextC = c + dc[di]; if (nextR >= 0 && nextR < rows && nextC >= 0 && nextC < cols && !visited[nextR][nextC]) { r = nextR; c = nextC; } else { di = (di + 1) % 4; r += dr[di]; c += dc[di]; } } return result; }}Complexity
Time
O(m * n)
Space
O(m * n)
Trade-offs
Pros
Conceptually straightforward, as it directly simulates the path.
Easy to understand the logic of moving and turning.
Cons
Requires extra space proportional to the size of the matrix, which can be significant for large matrices.
A more space-efficient approach is to view the matrix as a series of concentric layers or rectangles. We traverse the outermost layer, then shrink the boundaries to move to the next inner layer, and repeat this process until all elements are visited. This method avoids the need for an extra visited matrix.
Algorithm
- Initialize an empty list
result. - If the input matrix is empty, return the empty list.
- Initialize four pointers to define the boundaries of the current layer:
top = 0,bottom = matrix.length - 1,left = 0,right = matrix[0].length - 1. - Start a loop that continues as long as
top <= bottomandleft <= right.- Traverse Right: Iterate from
lefttorightalong thetoprow, addingmatrix[top][i]toresult. After this traversal, incrementtopto shrink the boundary. - Traverse Down: Iterate from
toptobottomalong therightcolumn, addingmatrix[i][right]toresult. After this, decrementright. - Check for single row/column cases: Before traversing left and up, check if
top <= bottomandleft <= right. This is crucial to handle matrices that are just a single row or a single column, preventing elements from being added twice. - Traverse Left: Iterate from
righttoleft(in reverse) along thebottomrow, addingmatrix[bottom][i]toresult. Then, decrementbottom. - Traverse Up: Iterate from
bottomtotop(in reverse) along theleftcolumn, addingmatrix[i][left]toresult. Then, incrementleft.
- Traverse Right: Iterate from
- The loop terminates when the boundaries cross, and all layers have been traversed. Return the
resultlist.
Walkthrough
This approach works by maintaining four pointers: top, bottom, left, and right, which represent the boundaries of the current layer of the matrix we are traversing. In each iteration of a main loop, we perform four traversals:
- Move from
lefttorightalong thetopboundary. - Move from
toptobottomalong therightboundary. - Move from
righttoleftalong thebottomboundary. - Move from
bottomtotopalong theleftboundary.
After each full cycle, we 'shrink' the boundaries inwards (top++, bottom--, left++, right--) to define the next, smaller concentric rectangle. The process continues until the boundaries cross each other (top > bottom or left > right). Special checks are needed after the first two traversals to correctly handle matrices with a single row or column.
class Solution { public List<Integer> spiralOrder(int[][] matrix) { List<Integer> result = new ArrayList<>(); if (matrix == null || matrix.length == 0) { return result; } int m = matrix.length; int n = matrix[0].length; int top = 0, bottom = m - 1; int left = 0, right = n - 1; while (top <= bottom && left <= right) { // Traverse Right for (int i = left; i <= right; i++) { result.add(matrix[top][i]); } top++; // Traverse Down for (int i = top; i <= bottom; i++) { result.add(matrix[i][right]); } right--; if (top <= bottom) { // Check if there's a row to traverse left // Traverse Left for (int i = right; i >= left; i--) { result.add(matrix[bottom][i]); } bottom--; } if (left <= right) { // Check if there's a column to traverse up // Traverse Up for (int i = bottom; i >= top; i--) { result.add(matrix[i][left]); } left++; } } return result; }}Complexity
Time
O(m * n)
Space
O(1)
Trade-offs
Pros
Highly space-efficient, using only constant extra space.
Elegant solution that maps well to the problem's structure.
Cons
The logic for handling boundary conditions and edge cases (like single-row or single-column matrices) can be slightly more complex to implement correctly.
Solutions
Solution
public class Solution { public IList < int > SpiralOrder(int[][] matrix) { int m = matrix.Length, n = matrix[0].Length; int[] dirs = new int[] { 0, 1, 0, -1, 0 }; IList < int > ans = new List < int > (); bool[, ] visited = new bool[m, n]; for (int h = m * n, i = 0, j = 0, k = 0; h > 0; --h) { ans.Add(matrix[i][j]); visited[i, j] = true; int x = i + dirs[k], y = j + dirs[k + 1]; if (x < 0 || x >= m || y < 0 || y >= n || visited[x, y]) { k = (k + 1) % 4; } i += dirs[k]; j += dirs[k + 1]; } return ans; }}class Solution {public List<Integer> spiralOrder(int[][] matrix) { int m = matrix.length, n = matrix[0].length; int[] dirs = {0, 1, 0, -1, 0}; int i = 0, j = 0, k = 0; List<Integer> ans = new ArrayList<>(); boolean[][] vis = new boolean[m][n]; for (int h = m * n; h > 0; --h) { ans.add(matrix[i][j]); vis[i][j] = true; int x = i + dirs[k], y = j + dirs[k + 1]; if (x < 0 || x >= m || y < 0 || y >= n || vis[x][y]) { k = (k + 1) % 4; } i += dirs[k]; j += dirs[k + 1]; } return ans; }}/** * @param {number[][]} matrix * @return {number[]} */ var spiralOrder = function (matrix) { const m = matrix.length; const n = matrix[0].length; const ans = []; const vis = new Array(m).fill(0).map(() => new Array(n).fill(false)); const dirs = [0, 1, 0, -1, 0]; for (let h = m * n, i = 0, j = 0, k = 0; h > 0; --h) { ans.push(matrix[i][j]); vis[i][j] = true; const x = i + dirs[k]; const y = j + dirs[k + 1]; if (x < 0 || x >= m || y < 0 || y >= n || vis[x][y]) { k = (k + 1) % 4; } i += dirs[k]; j += dirs[k + 1]; } return ans; };class Solution {public: vector<int> spiralOrder(vector<vector<int>> &matrix) { int m = matrix.size(), n = matrix[0].size(); int dirs[5] = {0, 1, 0, -1, 0}; int i = 0, j = 0, k = 0; vector<int> ans; bool vis[m][n]; memset(vis, false, sizeof(vis)); for (int h = m * n; h; --h) { ans.push_back(matrix[i][j]); vis[i][j] = true; int x = i + dirs[k], y = j + dirs[k + 1]; if (x < 0 || x >= m || y < 0 || y >= n || vis[x][y]) { k = (k + 1) % 4; } i += dirs[k]; j += dirs[k + 1]; } return ans; }};class Solution : def spiralOrder ( self , matrix : List [ List [ int ]]) -> List [ int ]: m , n = len ( matrix ), len ( matrix [ 0 ]) dirs = ( 0 , 1 , 0 , - 1 , 0 ) i = j = k = 0 ans = [] vis = set () for _ in range ( m * n ): ans . append ( matrix [ i ][ j ]) vis . add (( i , j )) x , y = i + dirs [ k ], j + dirs [ k + 1 ] if not 0 <= x < m or not 0 <= y < n or ( x , y ) in vis : k = ( k + 1 ) % 4 i = i + dirs [ k ] j = j + dirs [ k + 1 ] return ansVideo walkthrough
Newsletter
One sharp idea, every week
System design and interview prep — short enough to finish.
No spam. Unsubscribe anytime.
Practice
Same difficulty — related problems to reinforce the pattern.