2326. Spiral Matrix IV
Problem Description
You need to create a matrix filled with values from a linked list in a spiral pattern.
Given:
- Two integers
m
andn
representing the dimensions of a matrix (m rows × n columns) - The
head
of a linked list containing integers
Your task is to:
- Create an
m × n
matrix - Fill the matrix with values from the linked list in a spiral order (clockwise), starting from the top-left corner
(0, 0)
- The spiral pattern moves: right → down → left → up → right (and repeats)
- If the linked list has fewer elements than
m × n
, fill the remaining empty spaces with-1
- Return the generated matrix
For example, if you have a 3×3 matrix and a linked list with values [1,2,3,4,5,6]
, the spiral filling would look like:
- Start at top-left, move right: fill positions
(0,0)
,(0,1)
,(0,2)
with values1
,2
,3
- Turn down at the right edge: fill position
(1,2)
with value4
- Turn left at the bottom-right: fill position
(2,2)
with value5
- Continue left: fill position
(2,1)
with value6
- Remaining positions would be filled with
-1
The key challenge is to navigate the spiral pattern correctly, changing direction when you hit the matrix boundaries or already-filled cells.
Intuition
The spiral pattern is essentially about moving in one direction until we can't move anymore, then turning clockwise to the next direction. This is a classic simulation problem where we need to track our current position and direction.
Think about how you would manually fill a spiral: you'd start at the top-left corner and keep going right until you hit the edge. Then you'd turn down, go until you hit the bottom edge, turn left, and so on. The key insight is that we're always moving in one of four directions: right, down, left, or up, and we cycle through these directions in order.
To implement this, we can use a direction array to encode these four movements. The pattern (0, 1, 0, -1, 0)
cleverly encodes the four directions:
- Right:
(dirs[0], dirs[1])
=(0, 1)
- row stays same, column increases - Down:
(dirs[1], dirs[2])
=(1, 0)
- row increases, column stays same - Left:
(dirs[2], dirs[3])
=(0, -1)
- row stays same, column decreases - Up:
(dirs[3], dirs[4])
=(-1, 0)
- row decreases, column stays same
The direction index k
cycles through 0, 1, 2, 3 to pick consecutive pairs from this array.
For detecting when to turn, we have two conditions:
- We're about to go out of bounds (hit the matrix edge)
- We're about to hit a cell we've already filled (which won't be
-1
anymore)
Both conditions mean we can't continue in the current direction, so we turn clockwise by incrementing k
(modulo 4 to wrap around). We keep trying directions until we find a valid move. If the linked list still has elements but we can't move in any direction from the current position, that would be impossible given the problem constraints - there will always be a valid next position if we haven't filled all cells yet.
This simulation approach naturally handles the spiral pattern without explicitly tracking boundaries or layers, making the code cleaner and easier to understand.
Learn more about Linked List patterns.
Solution Approach
We implement the spiral traversal using simulation with direction vectors. Here's how the solution works step by step:
1. Initialize the Matrix
ans = [[-1] * n for _ in range(m)]
Create an m × n
matrix filled with -1
. This serves two purposes: it's our output matrix, and -1
marks unfilled cells that we can move into.
2. Set Up Position and Direction Tracking
i = j = k = 0 dirs = (0, 1, 0, -1, 0)
i, j
: Current position in the matrix (starting at top-left corner(0, 0)
)k
: Current direction index (0 = right, 1 = down, 2 = left, 3 = up)dirs
: Encodes the four direction vectors. Usingdirs[k]
anddirs[k+1]
gives us the row and column offsets for directionk
3. Main Traversal Loop
while 1:
ans[i][j] = head.val
head = head.next
if head is None:
break
For each node in the linked list:
- Fill the current position
(i, j)
with the node's value - Move to the next node
- If we've exhausted the linked list, exit
4. Find Next Valid Position
while 1: x, y = i + dirs[k], j + dirs[k + 1] if 0 <= x < m and 0 <= y < n and ans[x][y] == -1: i, j = x, y break k = (k + 1) % 4
This inner loop finds where to move next:
- Calculate the next position
(x, y)
based on current directionk
- Check if
(x, y)
is valid:- Within matrix bounds:
0 <= x < m
and0 <= y < n
- Not yet filled:
ans[x][y] == -1
- Within matrix bounds:
- If valid, update our position and continue
- If invalid, turn clockwise:
k = (k + 1) % 4
and try the next direction
The modulo operation ensures we cycle through directions: right (0) → down (1) → left (2) → up (3) → right (0) again.
Time Complexity: O(m × n)
- We visit each cell at most once
Space Complexity: O(m × n)
- For the output matrix (required by the problem)
The beauty of this approach is that it naturally handles the spiral pattern without explicitly tracking boundaries or spiral layers. The direction changes happen automatically when we hit filled cells or matrix edges.
Ready to land your dream job?
Unlock your dream job with a 3-minute evaluator for a personalized learning plan!
Start EvaluatorExample Walkthrough
Let's trace through a small example to see how the spiral filling works.
Given:
- Matrix dimensions:
m = 3, n = 4
(3 rows × 4 columns) - Linked list:
3 → 0 → 2 → 6 → 8 → 1 → 7 → 9 → 4 → 2 → 5 → 5
Step-by-step execution:
Initial Setup:
- Matrix filled with
-1
:[-1, -1, -1, -1] [-1, -1, -1, -1] [-1, -1, -1, -1]
- Position:
i = 0, j = 0
(top-left corner) - Direction:
k = 0
(moving right) - Direction vectors:
dirs = (0, 1, 0, -1, 0)
Filling Process:
-
Moving Right (k=0): direction vector is
(0, 1)
- Fill
(0,0)
with3
, move to(0,1)
- Fill
(0,1)
with0
, move to(0,2)
- Fill
(0,2)
with2
, move to(0,3)
- Fill
(0,3)
with6
- Try to move to
(0,4)
- out of bounds! Turn to k=1 (down)
Matrix state:
[3, 0, 2, 6] [-1, -1, -1, -1] [-1, -1, -1, -1]
- Fill
-
Moving Down (k=1): direction vector is
(1, 0)
- Fill
(1,3)
with8
, move to(2,3)
- Fill
(2,3)
with1
- Try to move to
(3,3)
- out of bounds! Turn to k=2 (left)
Matrix state:
[3, 0, 2, 6] [-1, -1, -1, 8] [-1, -1, -1, 1]
- Fill
-
Moving Left (k=2): direction vector is
(0, -1)
- Fill
(2,2)
with7
, move to(2,1)
- Fill
(2,1)
with9
, move to(2,0)
- Fill
(2,0)
with4
- Try to move to
(2,-1)
- out of bounds! Turn to k=3 (up)
Matrix state:
[3, 0, 2, 6] [-1, -1, -1, 8] [4, 9, 7, 1]
- Fill
-
Moving Up (k=3): direction vector is
(-1, 0)
- Fill
(1,0)
with2
- Try to move to
(0,0)
- already filled (not -1)! Turn to k=0 (right)
Matrix state:
[3, 0, 2, 6] [2, -1, -1, 8] [4, 9, 7, 1]
- Fill
-
Moving Right Again (k=0): direction vector is
(0, 1)
- Fill
(1,1)
with5
, move to(1,2)
- Fill
(1,2)
with5
- Linked list exhausted - done!
- Fill
Final Matrix:
[3, 0, 2, 6] [2, 5, 5, 8] [4, 9, 7, 1]
The algorithm successfully filled the matrix in spiral order: starting from top-left, going right along the top row, down the right column, left along the bottom row, up the left column, and finally right again to fill the remaining center cells. The direction changes happened automatically when we hit boundaries or already-filled cells.
Solution Implementation
1# Definition for singly-linked list.
2# class ListNode:
3# def __init__(self, val=0, next=None):
4# self.val = val
5# self.next = next
6
7from typing import List, Optional
8
9class Solution:
10 def spiralMatrix(self, m: int, n: int, head: Optional[ListNode]) -> List[List[int]]:
11 """
12 Fill an m x n matrix in spiral order with values from a linked list.
13 Empty cells are filled with -1.
14
15 Args:
16 m: Number of rows in the matrix
17 n: Number of columns in the matrix
18 head: Head of the linked list containing values to fill
19
20 Returns:
21 2D list representing the filled matrix
22 """
23 # Initialize matrix with -1 values
24 matrix = [[-1] * n for _ in range(m)]
25
26 # Starting position and direction index
27 row, col = 0, 0
28 direction_idx = 0
29
30 # Direction vectors: right(0,1), down(1,0), left(0,-1), up(-1,0)
31 # Stored as (row_delta, col_delta) pairs in sequence
32 directions = (0, 1, 0, -1, 0)
33
34 # Fill matrix with linked list values in spiral order
35 while True:
36 # Place current node value at current position
37 matrix[row][col] = head.val
38
39 # Move to next node
40 head = head.next
41 if head is None:
42 break
43
44 # Find next valid position
45 while True:
46 # Calculate next position based on current direction
47 next_row = row + directions[direction_idx]
48 next_col = col + directions[direction_idx + 1]
49
50 # Check if next position is valid (within bounds and unvisited)
51 if (0 <= next_row < m and
52 0 <= next_col < n and
53 matrix[next_row][next_col] == -1):
54 row, col = next_row, next_col
55 break
56
57 # Change direction clockwise if current direction is blocked
58 direction_idx = (direction_idx + 1) % 4
59
60 return matrix
61
1/**
2 * Definition for singly-linked list.
3 * public class ListNode {
4 * int val;
5 * ListNode next;
6 * ListNode() {}
7 * ListNode(int val) { this.val = val; }
8 * ListNode(int val, ListNode next) { this.val = val; this.next = next; }
9 * }
10 */
11class Solution {
12 public int[][] spiralMatrix(int m, int n, ListNode head) {
13 // Initialize matrix with -1 as default value for unfilled cells
14 int[][] matrix = new int[m][n];
15 for (int[] row : matrix) {
16 Arrays.fill(row, -1);
17 }
18
19 // Current position coordinates
20 int currentRow = 0;
21 int currentCol = 0;
22
23 // Direction index for spiral traversal (0: right, 1: down, 2: left, 3: up)
24 int directionIndex = 0;
25
26 // Direction vectors: right(0,1), down(1,0), left(0,-1), up(-1,0)
27 final int[] directionOffsets = {0, 1, 0, -1, 0};
28
29 // Fill the matrix in spiral order with values from linked list
30 while (true) {
31 // Place current node value at current position
32 matrix[currentRow][currentCol] = head.val;
33
34 // Move to next node
35 head = head.next;
36 if (head == null) {
37 break; // No more nodes to process
38 }
39
40 // Find next valid position in spiral order
41 while (true) {
42 // Calculate next position based on current direction
43 int nextRow = currentRow + directionOffsets[directionIndex];
44 int nextCol = currentCol + directionOffsets[directionIndex + 1];
45
46 // Check if next position is valid and unvisited
47 if (nextRow >= 0 && nextRow < m &&
48 nextCol >= 0 && nextCol < n &&
49 matrix[nextRow][nextCol] == -1) {
50 // Move to next position
51 currentRow = nextRow;
52 currentCol = nextCol;
53 break;
54 }
55
56 // Change direction clockwise if current direction is blocked
57 directionIndex = (directionIndex + 1) % 4;
58 }
59 }
60
61 return matrix;
62 }
63}
64
1/**
2 * Definition for singly-linked list.
3 * struct ListNode {
4 * int val;
5 * ListNode *next;
6 * ListNode() : val(0), next(nullptr) {}
7 * ListNode(int x) : val(x), next(nullptr) {}
8 * ListNode(int x, ListNode *next) : val(x), next(next) {}
9 * };
10 */
11class Solution {
12public:
13 vector<vector<int>> spiralMatrix(int m, int n, ListNode* head) {
14 // Initialize the matrix with -1 as default value
15 vector<vector<int>> matrix(m, vector<int>(n, -1));
16
17 // Starting position and direction index
18 int currentRow = 0;
19 int currentCol = 0;
20 int directionIndex = 0;
21
22 // Direction vectors: right, down, left, up
23 // dirs[i] and dirs[i+1] represent row and column changes respectively
24 const int directions[5] = {0, 1, 0, -1, 0};
25
26 // Fill the matrix in spiral order with values from the linked list
27 while (true) {
28 // Place current node value at current position
29 matrix[currentRow][currentCol] = head->val;
30
31 // Move to next node
32 head = head->next;
33
34 // If no more nodes, stop filling
35 if (!head) {
36 break;
37 }
38
39 // Find the next valid position to move
40 while (true) {
41 // Calculate next position based on current direction
42 int nextRow = currentRow + directions[directionIndex];
43 int nextCol = currentCol + directions[directionIndex + 1];
44
45 // Check if next position is valid and unvisited
46 if (nextRow >= 0 && nextRow < m &&
47 nextCol >= 0 && nextCol < n &&
48 matrix[nextRow][nextCol] == -1) {
49 // Move to the next position
50 currentRow = nextRow;
51 currentCol = nextCol;
52 break;
53 }
54
55 // Change direction (rotate clockwise)
56 directionIndex = (directionIndex + 1) % 4;
57 }
58 }
59
60 return matrix;
61 }
62};
63
1/**
2 * Definition for singly-linked list.
3 * class ListNode {
4 * val: number
5 * next: ListNode | null
6 * constructor(val?: number, next?: ListNode | null) {
7 * this.val = (val===undefined ? 0 : val)
8 * this.next = (next===undefined ? null : next)
9 * }
10 * }
11 */
12
13/**
14 * Creates a spiral matrix filled with values from a linked list
15 * @param m - Number of rows in the matrix
16 * @param n - Number of columns in the matrix
17 * @param head - Head of the linked list containing values to fill
18 * @returns A 2D matrix filled in spiral order with linked list values
19 */
20function spiralMatrix(m: number, n: number, head: ListNode | null): number[][] {
21 // Initialize matrix with -1 as default value
22 const matrix: number[][] = Array.from({ length: m }, () => Array(n).fill(-1));
23
24 // Direction vectors for right, down, left, up movements
25 // [right: (0,1), down: (1,0), left: (0,-1), up: (-1,0)]
26 const directions: number[] = [0, 1, 0, -1, 0];
27
28 // Current position (row, column) and direction index
29 let currentRow: number = 0;
30 let currentCol: number = 0;
31 let directionIndex: number = 0;
32
33 // Fill the matrix in spiral order
34 while (true) {
35 // Place current linked list value at current position
36 matrix[currentRow][currentCol] = head.val;
37
38 // Move to next node in linked list
39 head = head.next;
40
41 // Exit if we've processed all nodes
42 if (!head) {
43 break;
44 }
45
46 // Find next valid position
47 while (true) {
48 // Calculate next position based on current direction
49 const nextRow: number = currentRow + directions[directionIndex];
50 const nextCol: number = currentCol + directions[directionIndex + 1];
51
52 // Check if next position is valid (within bounds and unvisited)
53 if (nextRow >= 0 && nextRow < m &&
54 nextCol >= 0 && nextCol < n &&
55 matrix[nextRow][nextCol] === -1) {
56 // Move to next position
57 currentRow = nextRow;
58 currentCol = nextCol;
59 break;
60 }
61
62 // Change direction clockwise if current direction is blocked
63 directionIndex = (directionIndex + 1) % 4;
64 }
65 }
66
67 return matrix;
68}
69
Time and Space Complexity
Time Complexity: O(m × n)
The algorithm iterates through each cell of the m × n
matrix exactly once. The outer while loop continues until all values from the linked list are placed or we reach the end of the list (whichever comes first). Each cell is visited at most once to place a value. The inner while loop for direction changes has at most 4 iterations (constant time) for each placement, which doesn't affect the overall complexity. Therefore, the time complexity is O(m × n)
.
Space Complexity: O(m × n)
The algorithm creates a 2D matrix ans
with dimensions m × n
to store the result. This matrix requires O(m × n)
space. The additional variables (i
, j
, k
, dirs
, x
, y
) use only constant space O(1)
. Since the dominant space usage comes from the result matrix, the overall space complexity is O(m × n)
.
Learn more about how to find time and space complexity quickly.
Common Pitfalls
1. Infinite Loop When All Directions Are Blocked
The most critical pitfall in this spiral matrix implementation is the potential for an infinite loop in the inner direction-finding loop. This occurs when the current position is surrounded by filled cells or boundaries in all four directions, but there are still nodes remaining in the linked list.
When This Happens:
- In smaller matrices where the spiral completes before the linked list is exhausted
- Example: A 2×2 matrix with a linked list containing 5+ elements
The Problem:
# After filling all 4 cells in a 2x2 matrix: while True: next_row = row + directions[direction_idx] next_col = col + directions[direction_idx + 1] if (0 <= next_row < m and 0 <= next_col < n and matrix[next_row][next_col] == -1): row, col = next_row, next_col break direction_idx = (direction_idx + 1) % 4 # This loops forever if no valid position exists!
Solution: Add a counter to track direction changes and break after checking all four directions:
while head:
matrix[row][col] = head.val
head = head.next
if head is None:
break
# Track how many directions we've tried
directions_tried = 0
while directions_tried < 4:
next_row = row + directions[direction_idx]
next_col = col + directions[direction_idx + 1]
if (0 <= next_row < m and
0 <= next_col < n and
matrix[next_row][next_col] == -1):
row, col = next_row, next_col
break
direction_idx = (direction_idx + 1) % 4
directions_tried += 1
# If we've tried all directions and found no valid position, stop
if directions_tried == 4:
break
2. Incorrect Direction Vector Indexing
Another common mistake is incorrectly accessing the direction vectors, especially forgetting that the directions array encodes pairs of values.
The Problem:
# Incorrect: treating directions as separate arrays directions_row = [0, 1, 0, -1] directions_col = [1, 0, -1, 0] next_row = row + directions_row[direction_idx] # Extra array overhead next_col = col + directions_col[direction_idx]
Why the Original is Better:
The single array directions = (0, 1, 0, -1, 0)
is more efficient and elegant, but requires careful indexing with directions[k]
and directions[k+1]
.
3. Not Handling Edge Case of Empty Linked List
If the linked list is empty (head is None
), the code would crash when trying to access head.val
.
Solution: Add an initial check:
def spiralMatrix(self, m: int, n: int, head: Optional[ListNode]) -> List[List[int]]:
matrix = [[-1] * n for _ in range(m)]
if not head:
return matrix
# Rest of the implementation...
How does merge sort divide the problem into subproblems?
Recommended Readings
Linked List Cycle Given a linked list with potentially a loop determine whether the linked list from the first node contains a cycle in it For bonus points do this with constant space Parameters nodes The first node of a linked list with potentially a loop Result Whether there is a loop contained
Coding Interview Patterns Your Personal Dijkstra's Algorithm to Landing Your Dream Job The goal of AlgoMonster is to help you get a job in the shortest amount of time possible in a data driven way We compiled datasets of tech interview problems and broke them down by patterns This way
Recursion Recursion is one of the most important concepts in computer science Simply speaking recursion is the process of a function calling itself Using a real life analogy imagine a scenario where you invite your friends to lunch https assets algo monster recursion jpg You first call Ben and ask
Want a Structured Path to Master System Design Too? Don’t Miss This!