[Medium] 1424. Diagonal Traverse II
Given a 2D integer array nums, return all elements of nums in diagonal order.
Examples
Example 1:
Input: nums = [[1,2,3],[4,5,6],[7,8,9]]
Output: [1,4,2,7,5,3,8,6,9]
Example 2:
Input: nums = [[1,2,3,4,5],[6,7],[8],[9,10,11],[12,13,14,15,16]]
Output: [1,6,2,8,7,3,9,4,12,10,5,13,11,14,15,16]
Example 3:
Input: nums = [[1,2,3],[4],[5,6,7],[8],[9,10,11]]
Output: [1,4,2,5,3,8,6,9,7,10,11]
Example 4:
Input: nums = [[1,2,3,4,5,6]]
Output: [1,2,3,4,5,6]
Constraints
1 <= nums.length <= 10^51 <= nums[i].length <= 10^51 <= sum(nums[i].length) <= 10^51 <= nums[i][j] <= 10^9
Thinking Process
- Diagonal Property: Elements on the same diagonal share the same
row + colsum.
- BFS visits nodes in non-decreasing distance from the source.
- Queue guarantees shortest path in unweighted graphs.
- Process level by level when counting layers or distances.
Common Approaches
Typical techniques for this pattern:
| Approach | Time | Space | Notes |
|---|---|---|---|
| Queue BFS (this problem) | O(n) | O(n) | Shortest path in unweighted graphs |
| Multi-source BFS | O(n) | O(n) | Start from all sources simultaneously |
| 0-1 BFS / deque | O(n) | O(n) | Weights 0 or 1 |
| Level-order BFS | O(n) | O(w) | Process by depth/layer |
Solution
Time Complexity: O(n) where n is total number of elements
Space Complexity: O(n)
We can solve this problem using two approaches:
- Hash Map Grouping: Group elements by their diagonal index (row + col), then iterate through diagonals in order.
- BFS Traversal: Use a queue to traverse diagonally, processing elements level by level.
Solution 1: Hash Map Grouping (Java20 Optimized)
Key Insight: Elements on the same diagonal have the same sum of row and column indices (row + col). We can group all elements by this diagonal index, then iterate through diagonals in ascending order.
// import java.util.*;
class Solution {
public int[] findDiagonalOrder(int[][] nums) {
HashMap<Integer, int[]> groups = new HashMap<Integer, int[]>();
// Traverse from bottom to top to maintain diagonal order
for (int row = nums.length - 1; row >= 0; row--) {
for (int col = 0; col < (int)nums[row].size(); col++) {
int diagonal = row + col;
groups.computeIfAbsent(diagonal, k -> new ArrayList<>()).add(nums[row][col]);
}
}
List<Integer> rtn = new ArrayList<>();
// Process diagonals in order (0, 1, 2, ...)
int curr = 0;
while (groups.find(curr) != groups.iterator()) {
for (int num : groups[curr]) {
rtn.add(num);
}
curr++;
}
return rtn;
}
}
Solution Explanation
Approach: Queue BFS (this problem)
Key idea: 1. Diagonal Property: Elements on the same diagonal share the same row + col sum.
How the code works:
- Diagonal Property: Elements on the same diagonal share the same
row + colsum.- BFS visits nodes in non-decreasing distance from the source.
- Queue guarantees shortest path in unweighted graphs.
- Process level by level when counting layers or distances.
Walkthrough — input nums = [[1,2,3],[4,5,6],[7,8,9]], expected output [1,4,2,7,5,3,8,6,9]:
- Initialize variables from the problem setup.
- Apply the main loop / recursion until the condition is met.
- Confirm the result matches the expected output.
Time: O(n) - Each element is visited once · Space Complexity: O(n)
Java20 Optimizations:
groups.find(curr) != groups.end()for map lookup (Java20 compatible)reserve()to pre-allocate memory for efficiency- Range-based for loops for cleaner iteration
How it works:
- Group by Diagonal: For each element at
(row, col), calculatediagonal = row + coland add it to the corresponding group. - Bottom-to-Top Traversal: We traverse rows from bottom to top so that when we process diagonals in order, elements appear in the correct sequence.
- Sequential Processing: Process diagonals starting from 0, adding all elements in each diagonal group to the result.
Solution 2: BFS Traversal (Java20 Optimized)
Key Insight: We can use BFS starting from (0, 0) and traverse diagonally. For each cell, we explore:
- The cell below (if in first column):
(row + 1, 0) - The cell to the right:
(row, col + 1)
class Solution {
public int[] findDiagonalOrder(int[][] nums) {
queue<int[]> q;
q.offer(new int[] {0, 0});
List<Integer> rtn = new ArrayList<>();
while (!q.isEmpty()) {
auto [row, col] = q.get(0);
q.poll();
rtn.add(nums[row][col]);
// If in first column, add cell below
if (col == 0 && row + 1 < nums.length) {
q.offer({row + 1, col});
}
// Add cell to the right
if (col + 1 < (int)nums[row].size()) {
q.offer({row, col + 1});
}
}
return rtn;
}
}
Java20 Optimizations:
- Structured bindings
auto [row, col]for pair unpacking (Java17) reserve()for memory pre-allocation- Simplified type casting with
(int)instead ofstatic_cast<int>()
How it works:
- Start at Origin: Begin BFS from
(0, 0). - Process Current Cell: Add the current cell’s value to the result.
- Explore Neighbors:
- If in the first column (
col == 0), add the cell below:(row + 1, 0) - Always add the cell to the right:
(row, col + 1)
- If in the first column (
- Continue BFS: Process all cells in the queue until empty.
Step-by-Step Example
Input: nums = [[1,2,3],[4,5,6],[7,8,9]]
Solution 1: Hash Map Approach
| Step | Row | Col | Diagonal | Groups |
|---|---|---|---|---|
| 1 | 2 | 0 | 2 | {2: [7]} |
| 2 | 2 | 1 | 3 | {2: [7], 3: [8]} |
| 3 | 2 | 2 | 4 | {2: [7], 3: [8], 4: [9]} |
| 4 | 1 | 0 | 1 | {1: [4], 2: [7], 3: [8], 4: [9]} |
| 5 | 1 | 1 | 2 | {1: [4], 2: [7,5], 3: [8], 4: [9]} |
| 6 | 1 | 2 | 3 | {1: [4], 2: [7,5], 3: [8,6], 4: [9]} |
| 7 | 0 | 0 | 0 | {0: [1], 1: [4], 2: [7,5], 3: [8,6], 4: [9]} |
| 8 | 0 | 1 | 1 | {0: [1], 1: [4,2], 2: [7,5], 3: [8,6], 4: [9]} |
| 9 | 0 | 2 | 2 | {0: [1], 1: [4,2], 2: [7,5,3], 3: [8,6], 4: [9]} |
Processing diagonals:
- Diagonal 0:
[1]→[1] - Diagonal 1:
[4,2]→[1,4,2] - Diagonal 2:
[7,5,3]→[1,4,2,7,5,3] - Diagonal 3:
[8,6]→[1,4,2,7,5,3,8,6] - Diagonal 4:
[9]→[1,4,2,7,5,3,8,6,9]
Output: [1,4,2,7,5,3,8,6,9]
Solution 2: BFS Approach
| Step | Queue | Process | Result | Next Moves |
|---|---|---|---|---|
| 0 | [(0,0)] |
- | [] |
- |
| 1 | [(0,0)] |
(0,0) → 1 |
[1] |
(1,0), (0,1) |
| 2 | [(1,0), (0,1)] |
(1,0) → 4 |
[1,4] |
(2,0), (1,1) |
| 3 | [(0,1), (2,0), (1,1)] |
(0,1) → 2 |
[1,4,2] |
(0,2) |
| 4 | [(2,0), (1,1), (0,2)] |
(2,0) → 7 |
[1,4,2,7] |
(2,1) |
| 5 | [(1,1), (0,2), (2,1)] |
(1,1) → 5 |
[1,4,2,7,5] |
(1,2) |
| 6 | [(0,2), (2,1), (1,2)] |
(0,2) → 3 |
[1,4,2,7,5,3] |
- |
| 7 | [(2,1), (1,2)] |
(2,1) → 8 |
[1,4,2,7,5,3,8] |
(2,2) |
| 8 | [(1,2), (2,2)] |
(1,2) → 6 |
[1,4,2,7,5,3,8,6] |
- |
| 9 | [(2,2)] |
(2,2) → 9 |
[1,4,2,7,5,3,8,6,9] |
- |
Output: [1,4,2,7,5,3,8,6,9]
Visual Representation
Matrix:
0 1 2
0 [ 1 2 3 ]
1 [ 4 5 6 ]
2 [ 7 8 9 ]
Diagonals (row + col):
0 1 2
0 [ 0 1 2 ]
1 [ 1 2 3 ]
2 [ 2 3 4 ]
Diagonal Traversal:
Diagonal 0: [1]
Diagonal 1: [4, 2]
Diagonal 2: [7, 5, 3]
Diagonal 3: [8, 6]
Diagonal 4: [9]
Result: [1, 4, 2, 7, 5, 3, 8, 6, 9]
Complexity
Solution 1: Hash Map
- Time: O(n) - Each element is visited once
- Space: O(n) - Hash map stores all elements
Solution 2: BFS
- Time: O(n) - Each element is processed once
- Space: O(n) - Queue can contain up to O(n) elements in worst case
References
- LC 1424: Diagonal Traverse II on LeetCode
- LeetCode Discuss — LC 1424: Diagonal Traverse II
- LeetCode Editorial (may require premium)
Common Mistakes
- Skipping edge cases (empty input, single element, boundaries).
- Off-by-one errors in loops and index ranges.
- Forgetting to handle the case when no valid answer exists.
Key Takeaways
- Diagonal Property: Elements on the same diagonal share the same
row + colsum. - Traversal Order: Bottom-to-top traversal ensures correct ordering when processing diagonals sequentially.
- BFS Alternative: BFS naturally explores diagonals when we prioritize right moves over down moves.
- Jagged Arrays: Both solutions handle jagged arrays (rows of different lengths) correctly.