498. Diagonal Traverse
Problem Description
Given a 2D matrix with m
rows and n
columns, you need to return all elements of the matrix in diagonal order.
The diagonal traversal follows a zigzag pattern:
- Start from the top-left corner
mat[0][0]
- Move diagonally up-right until you hit a boundary
- Then move diagonally down-left until you hit a boundary
- Continue alternating between these two diagonal directions
For example, if you have a 3x3 matrix:
1 2 3 4 5 6 7 8 9
The diagonal order would be: [1, 2, 4, 7, 5, 3, 6, 8, 9]
The traversal pattern looks like:
- First diagonal (up-right):
1
- Second diagonal (down-left):
2, 4
- Third diagonal (up-right):
7, 5, 3
- Fourth diagonal (down-left):
6, 8
- Fifth diagonal (up-right):
9
The solution works by iterating through each diagonal line (there are m + n - 1
diagonals total). For each diagonal k
:
- It determines the starting position
(i, j)
based on whether we're still in the firstn
diagonals or beyond - It collects all elements along that diagonal from top-right to bottom-left
- If
k
is even (0, 2, 4...), it reverses the collected elements to achieve the zigzag pattern - All collected elements are added to the final result array
Intuition
The key observation is that elements on the same diagonal share a common property: the sum of their row and column indices is constant. For example, elements mat[0][2]
, mat[1][1]
, and mat[2][0]
all have i + j = 2
.
This gives us the insight that we can group elements by their diagonal number k
, where k = i + j
. Since the matrix has m
rows and n
columns, the diagonal sums range from 0
(top-left corner) to m + n - 2
(bottom-right corner), giving us exactly m + n - 1
diagonals.
For each diagonal k
, we need to find all valid (i, j)
pairs where i + j = k
. The constraints are:
0 <= i < m
(within row bounds)0 <= j < n
(within column bounds)i + j = k
From these constraints, we can derive that for diagonal k
:
- If
k < n
, we start from the top row (i = 0
) and columnj = k
- If
k >= n
, we've exceeded the rightmost column, so we start from the rightmost column (j = n - 1
) and rowi = k - n + 1
We then traverse diagonally by incrementing i
and decrementing j
until we hit a boundary.
The zigzag pattern emerges from alternating the direction of each diagonal. Notice that:
- Even-numbered diagonals (
k = 0, 2, 4...
) go upward (from bottom-left to top-right) - Odd-numbered diagonals (
k = 1, 3, 5...
) go downward (from top-right to bottom-left)
Since we always collect elements from top-right to bottom-left, we need to reverse the elements for even-numbered diagonals to achieve the upward direction.
Solution Approach
The implementation uses a fixed point traversal strategy for each diagonal. Here's how the algorithm works step by step:
-
Initialize variables: Get the dimensions
m
(rows) andn
(columns) of the matrix, and create an empty result listans
. -
Iterate through all diagonals: Loop through
k
from0
tom + n - 2
(inclusive), representing each diagonal. -
Determine starting position for diagonal
k
:- If
k < n
: Start from the top row, soi = 0
andj = k
- If
k >= n
: We've passed the last column, so start from the rightmost column withi = k - n + 1
andj = n - 1
- If
-
Collect elements along the diagonal: Create a temporary list
t
to store elements on the current diagonal. Starting from position(i, j)
, traverse diagonally by:- Adding
mat[i][j]
to the temporary list - Moving to the next position:
i += 1
(move down) andj -= 1
(move left) - Continue while
i < m
andj >= 0
(stay within matrix bounds)
- Adding
-
Handle zigzag pattern:
- If
k
is even (0, 2, 4...), reverse the temporary listt
usingt[::-1]
- This reversal converts the down-left traversal into an up-right direction for the final result
- If
-
Append to result: Extend the answer list with all elements from the current diagonal using
ans.extend(t)
-
Return the result: After processing all diagonals, return the complete
ans
list containing all matrix elements in diagonal order.
The algorithm has a time complexity of O(m * n)
since we visit each element exactly once, and a space complexity of O(1)
excluding the output array (or O(min(m, n))
if we count the temporary list t
which stores at most one diagonal's worth of elements).
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 walk through a small 3×2 matrix to illustrate the solution approach:
Matrix: 1 2 3 4 5 6
Step 1: Initialize
m = 3
(rows),n = 2
(columns)- Total diagonals =
m + n - 1 = 4
- Result list
ans = []
Step 2: Process diagonal k = 0
- Since
k < n
(0 < 2), start at:i = 0
,j = 0
- Collect elements along diagonal:
t = [1]
- Since k is even, reverse:
t = [1]
(no change for single element) - Add to result:
ans = [1]
Step 3: Process diagonal k = 1
- Since
k < n
(1 < 2), start at:i = 0
,j = 1
- Collect elements from (0,1) to (1,0):
- Add mat[0][1] = 2 →
t = [2]
- Move to (1,0): Add mat[1][0] = 3 →
t = [2, 3]
- Move to (2,-1): j < 0, stop
- Add mat[0][1] = 2 →
- Since k is odd, don't reverse:
t = [2, 3]
- Add to result:
ans = [1, 2, 3]
Step 4: Process diagonal k = 2
- Since
k >= n
(2 >= 2), start at:i = k - n + 1 = 1
,j = n - 1 = 1
- Collect elements from (1,1) to (2,0):
- Add mat[1][1] = 4 →
t = [4]
- Move to (2,0): Add mat[2][0] = 5 →
t = [4, 5]
- Move to (3,-1): i >= m, stop
- Add mat[1][1] = 4 →
- Since k is even, reverse:
t = [5, 4]
- Add to result:
ans = [1, 2, 3, 5, 4]
Step 5: Process diagonal k = 3
- Since
k >= n
(3 >= 2), start at:i = k - n + 1 = 2
,j = n - 1 = 1
- Collect elements from (2,1):
- Add mat[2][1] = 6 →
t = [6]
- Move to (3,0): i >= m, stop
- Add mat[2][1] = 6 →
- Since k is odd, don't reverse:
t = [6]
- Add to result:
ans = [1, 2, 3, 5, 4, 6]
Final Result: [1, 2, 3, 5, 4, 6]
The traversal pattern creates a zigzag:
- Diagonal 0 (even): ↗ moves up-right:
[1]
- Diagonal 1 (odd): ↙ moves down-left:
[2, 3]
- Diagonal 2 (even): ↗ moves up-right:
[5, 4]
- Diagonal 3 (odd): ↙ moves down-left:
[6]
Solution Implementation
1class Solution:
2 def findDiagonalOrder(self, mat: List[List[int]]) -> List[int]:
3 # Get matrix dimensions
4 rows, cols = len(mat), len(mat[0])
5 result = []
6
7 # Iterate through all diagonals (total: rows + cols - 1)
8 for diagonal_index in range(rows + cols - 1):
9 diagonal_elements = []
10
11 # Determine starting position for current diagonal
12 # If diagonal_index < cols, start from first row
13 # Otherwise, start from appropriate row below
14 start_row = 0 if diagonal_index < cols else diagonal_index - cols + 1
15 start_col = diagonal_index if diagonal_index < cols else cols - 1
16
17 # Traverse current diagonal from top-right to bottom-left
18 current_row = start_row
19 current_col = start_col
20 while current_row < rows and current_col >= 0:
21 diagonal_elements.append(mat[current_row][current_col])
22 current_row += 1
23 current_col -= 1
24
25 # For even-indexed diagonals, reverse the order (zigzag pattern)
26 if diagonal_index % 2 == 0:
27 diagonal_elements = diagonal_elements[::-1]
28
29 # Add diagonal elements to final result
30 result.extend(diagonal_elements)
31
32 return result
33
1class Solution {
2 public int[] findDiagonalOrder(int[][] mat) {
3 // Get matrix dimensions
4 int rows = mat.length;
5 int cols = mat[0].length;
6
7 // Initialize result array to store all elements
8 int[] result = new int[rows * cols];
9 int resultIndex = 0;
10
11 // Temporary list to store elements of each diagonal
12 List<Integer> diagonal = new ArrayList<>();
13
14 // Iterate through all diagonals (total: rows + cols - 1)
15 for (int diagonalNum = 0; diagonalNum < rows + cols - 1; diagonalNum++) {
16 // Calculate starting position for current diagonal
17 // If diagonal number < cols, start from first row
18 // Otherwise, start from the row that makes the diagonal reach the last column
19 int startRow = diagonalNum < cols ? 0 : diagonalNum - cols + 1;
20 int startCol = diagonalNum < cols ? diagonalNum : cols - 1;
21
22 // Traverse current diagonal from top-right to bottom-left
23 while (startRow < rows && startCol >= 0) {
24 diagonal.add(mat[startRow][startCol]);
25 startRow++;
26 startCol--;
27 }
28
29 // For even-numbered diagonals, reverse the order (bottom-left to top-right)
30 if (diagonalNum % 2 == 0) {
31 Collections.reverse(diagonal);
32 }
33
34 // Add diagonal elements to result array
35 for (int element : diagonal) {
36 result[resultIndex++] = element;
37 }
38
39 // Clear the temporary list for next diagonal
40 diagonal.clear();
41 }
42
43 return result;
44 }
45}
46
1class Solution {
2public:
3 vector<int> findDiagonalOrder(vector<vector<int>>& mat) {
4 int rows = mat.size();
5 int cols = mat[0].size();
6 vector<int> result;
7 vector<int> diagonal;
8
9 // Iterate through all diagonals (there are rows + cols - 1 diagonals total)
10 for (int diagonalIndex = 0; diagonalIndex < rows + cols - 1; ++diagonalIndex) {
11 // Determine the starting position for current diagonal
12 // For diagonals starting from first row: start at (0, diagonalIndex)
13 // For diagonals starting from first column: start at (diagonalIndex - cols + 1, cols - 1)
14 int startRow = (diagonalIndex < cols) ? 0 : diagonalIndex - cols + 1;
15 int startCol = (diagonalIndex < cols) ? diagonalIndex : cols - 1;
16
17 // Traverse the diagonal from top-right to bottom-left
18 while (startRow < rows && startCol >= 0) {
19 diagonal.push_back(mat[startRow][startCol]);
20 ++startRow;
21 --startCol;
22 }
23
24 // For even-indexed diagonals, reverse the collected elements
25 // This creates the zigzag pattern (up-right for even, down-left for odd)
26 if (diagonalIndex % 2 == 0) {
27 ranges::reverse(diagonal);
28 }
29
30 // Append the diagonal elements to the result
31 result.insert(result.end(), diagonal.begin(), diagonal.end());
32
33 // Clear the temporary diagonal vector for the next iteration
34 diagonal.clear();
35 }
36
37 return result;
38 }
39};
40
1/**
2 * Traverses a matrix in diagonal order (zigzag pattern)
3 * @param mat - The input matrix to traverse
4 * @returns Array containing matrix elements in diagonal order
5 */
6function findDiagonalOrder(mat: number[][]): number[] {
7 const rowCount: number = mat.length;
8 const colCount: number = mat[0].length;
9 const result: number[] = [];
10
11 // Process each diagonal line, total diagonals = rowCount + colCount - 1
12 for (let diagonalIndex = 0; diagonalIndex < rowCount + colCount - 1; diagonalIndex++) {
13 const currentDiagonal: number[] = [];
14
15 // Calculate starting position for current diagonal
16 // If diagonalIndex < colCount, start from first row
17 // Otherwise, start from subsequent rows
18 let currentRow: number = diagonalIndex < colCount ? 0 : diagonalIndex - colCount + 1;
19 let currentCol: number = diagonalIndex < colCount ? diagonalIndex : colCount - 1;
20
21 // Traverse current diagonal from top-right to bottom-left
22 while (currentRow < rowCount && currentCol >= 0) {
23 currentDiagonal.push(mat[currentRow][currentCol]);
24 currentRow++;
25 currentCol--;
26 }
27
28 // For even-indexed diagonals, reverse the direction (bottom-left to top-right)
29 if (diagonalIndex % 2 === 0) {
30 currentDiagonal.reverse();
31 }
32
33 // Add current diagonal elements to result
34 result.push(...currentDiagonal);
35 }
36
37 return result;
38}
39
Time and Space Complexity
The time complexity is O(m × n)
, where m
is the number of rows and n
is the number of columns in the matrix. This is because:
- The outer loop runs
m + n - 1
times (total number of diagonals) - For each diagonal
k
, the inner while loop processes elements along that diagonal - The total number of elements processed across all diagonals is exactly
m × n
(every element in the matrix is visited exactly once) - The reversal operation
t[::-1]
takesO(len(t))
time, but the sum of all diagonal lengths equalsm × n
The space complexity is O(min(m, n))
for the temporary list t
that stores elements of each diagonal. The longest diagonal has length min(m, n)
, which is the maximum space used by t
at any point. However, if we ignore the space used for the answer array ans
(as mentioned in the reference), and only consider auxiliary space, the space complexity can be considered O(min(m, n))
. The reference answer states O(1)
which would be accurate if we optimize the code to directly append to the result without using the temporary list t
, but with the current implementation using temporary storage t
, it's technically O(min(m, n))
.
Common Pitfalls
1. Incorrect Starting Position Calculation
One of the most common mistakes is incorrectly determining the starting position for each diagonal, especially when transitioning from diagonals that start on the top row to those that start on the rightmost column.
Pitfall Example:
# Incorrect approach - off by one error start_row = 0 if diagonal_index <= cols else diagonal_index - cols # This would cause index out of bounds for later diagonals
Solution: Ensure the correct formula when diagonal_index >= cols:
start_row = 0 if diagonal_index < cols else diagonal_index - cols + 1
The +1
is crucial because when we reach diagonal_index = cols, we need to start from row 1, not row 0.
2. Boundary Check Errors During Traversal
Another frequent mistake is using incorrect boundary conditions while traversing each diagonal, leading to IndexError or missing elements.
Pitfall Example:
# Wrong boundary check while current_row <= rows and current_col >= 0: # Should be < rows, not <= rows diagonal_elements.append(mat[current_row][current_col])
Solution: Use strict less-than comparison for row bounds:
while current_row < rows and current_col >= 0: diagonal_elements.append(mat[current_row][current_col])
3. Incorrect Zigzag Pattern Implementation
Developers often reverse the wrong diagonals or forget to implement the zigzag pattern entirely.
Pitfall Example:
# Reversing odd diagonals instead of even if diagonal_index % 2 == 1: # Wrong - should reverse even diagonals diagonal_elements = diagonal_elements[::-1]
Solution: Remember that we reverse even-indexed diagonals (0, 2, 4...) to achieve the up-right traversal effect:
if diagonal_index % 2 == 0: diagonal_elements = diagonal_elements[::-1]
4. Edge Case: Single Row or Column Matrix
The algorithm might fail or produce incorrect results for matrices with only one row or one column if not handled properly.
Pitfall Example: Not considering that a 1×n or m×1 matrix still needs proper diagonal traversal.
Solution: The provided algorithm naturally handles these cases, but always test with:
- Single row matrix: [[1, 2, 3]] → [1, 2, 3]
- Single column matrix: [[1], [2], [3]] → [1, 2, 3]
- Single element matrix: [[1]] → [1]
5. Inefficient Memory Usage
Creating unnecessary intermediate data structures or not properly managing the temporary diagonal list.
Pitfall Example:
# Creating a new list for every append operation result = result + diagonal_elements # Creates new list each time
Solution:
Use extend()
method for better performance:
result.extend(diagonal_elements) # Modifies list in place
Depth first search is equivalent to which of the tree traversal order?
Recommended Readings
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
Runtime Overview When learning about algorithms and data structures you'll frequently encounter the term time complexity This concept is fundamental in computer science and offers insights into how long an algorithm takes to complete given a certain input size What is Time Complexity Time complexity represents the amount of time
Want a Structured Path to Master System Design Too? Don’t Miss This!