3417. Zigzag Grid Traversal With Skip
Problem Description
You have a 2D grid of positive integers with m
rows and n
columns. Your task is to traverse this grid in a zigzag pattern while skipping every other cell you encounter.
The zigzag traversal works like this:
- Start at the top-left corner at position
(0, 0)
- Move right across the first row until you reach the end
- Drop down to the second row and move left until you reach the beginning
- Continue this pattern, alternating between moving right and left for each subsequent row
The key constraint is that you must skip every alternate cell during your traversal. This means:
- Visit the 1st cell, skip the 2nd cell
- Visit the 3rd cell, skip the 4th cell
- And so on...
The skipping continues across row boundaries - if you end a row on a "skip", you'll start the next row on a "visit" and vice versa.
For example, with a 3×3 grid:
1 2 3 4 5 6 7 8 9
The zigzag order would be: 1, 2, 3, 6, 5, 4, 7, 8, 9
After applying the skip pattern: 1
(visit), skip 2
, 3
(visit), skip 6
, 5
(visit), skip 4
, 7
(visit), skip 8
, 9
(visit)
Result: [1, 3, 5, 7, 9]
Return an array containing the values of only the visited cells in the order they were encountered during the zigzag traversal.
Intuition
The key insight is to recognize that we need to handle two distinct patterns: the zigzag traversal and the alternating skip pattern.
For the zigzag traversal, we notice that:
- Even-indexed rows (0, 2, 4, ...) are traversed left to right
- Odd-indexed rows (1, 3, 5, ...) are traversed right to left
Instead of implementing complex logic to traverse backwards, we can simply reverse the odd rows before processing them. This transforms the problem into a straightforward left-to-right traversal of all rows.
For the skip pattern, we need to maintain a global state that tracks whether the current cell should be visited or skipped. This state must persist across row boundaries - if we end row i
on a "skip", we should start row i+1
on a "visit".
We can use a boolean flag ok
that:
- When
True
: add the current element to our result - When
False
: skip the current element - Toggle after processing each cell, regardless of whether we visited or skipped it
By combining these two ideas:
- Process rows in order, reversing odd-indexed rows to simulate the zigzag
- Maintain a single boolean that toggles for every cell encountered
This gives us a clean solution that handles both the zigzag pattern and the skip logic without complex index calculations or special cases for row boundaries.
Solution Approach
The implementation follows a simulation approach where we process the grid row by row while maintaining the zigzag pattern and skip logic.
Step 1: Initialize variables
- Create a boolean flag
ok = True
to track whether the current cell should be visited - Create an empty list
ans
to store the visited cell values
Step 2: Process each row with zigzag logic
for i, row in enumerate(grid):
if i % 2:
row.reverse()
- Iterate through each row with its index
- For odd-indexed rows (when
i % 2
is truthy), reverse the row in-place - This simulates moving right-to-left without changing our traversal direction
Step 3: Process cells with skip pattern
for x in row: if ok: ans.append(x) ok = not ok
- For each element
x
in the current row (possibly reversed) - Check if
ok
isTrue
- if so, add the element to our answer - Toggle
ok
usingok = not ok
regardless of whether we visited or skipped - This ensures the skip pattern continues correctly across row boundaries
Step 4: Return the result
- After processing all rows, return the
ans
list containing visited cells in order
The time complexity is O(m × n)
where m
is the number of rows and n
is the number of columns, as we visit every cell once. The space complexity is O(k)
where k
is the number of visited cells (approximately half of all cells), not counting the space needed for the output array.
The elegance of this solution lies in its simplicity - by reversing odd rows, we transform a complex bidirectional traversal into a simple unidirectional one, while a single boolean efficiently handles the alternating skip pattern.
Ready to land your dream job?
Unlock your dream job with a 5-minute evaluator for a personalized learning plan!
Start EvaluatorExample Walkthrough
Let's trace through a 3×4 grid to illustrate the solution approach:
Grid: 1 2 3 4 5 6 7 8 9 10 11 12
Initial State:
ok = True
(start by visiting)ans = []
(empty result list)
Processing Row 0 (index = 0, even):
- Row remains as-is:
[1, 2, 3, 4]
(no reversal for even rows) - Process element
1
:ok = True
, so append 1 to ans →ans = [1]
, toggle ok →ok = False
- Process element
2
:ok = False
, so skip, toggle ok →ok = True
- Process element
3
:ok = True
, so append 3 to ans →ans = [1, 3]
, toggle ok →ok = False
- Process element
4
:ok = False
, so skip, toggle ok →ok = True
Processing Row 1 (index = 1, odd):
- Reverse the row:
[5, 6, 7, 8]
becomes[8, 7, 6, 5]
- Process element
8
:ok = True
, so append 8 to ans →ans = [1, 3, 8]
, toggle ok →ok = False
- Process element
7
:ok = False
, so skip, toggle ok →ok = True
- Process element
6
:ok = True
, so append 6 to ans →ans = [1, 3, 8, 6]
, toggle ok →ok = False
- Process element
5
:ok = False
, so skip, toggle ok →ok = True
Processing Row 2 (index = 2, even):
- Row remains as-is:
[9, 10, 11, 12]
(no reversal for even rows) - Process element
9
:ok = True
, so append 9 to ans →ans = [1, 3, 8, 6, 9]
, toggle ok →ok = False
- Process element
10
:ok = False
, so skip, toggle ok →ok = True
- Process element
11
:ok = True
, so append 11 to ans →ans = [1, 3, 8, 6, 9, 11]
, toggle ok →ok = False
- Process element
12
:ok = False
, so skip, toggle ok →ok = True
Final Result: [1, 3, 8, 6, 9, 11]
The zigzag traversal order was: 1, 2, 3, 4, 8, 7, 6, 5, 9, 10, 11, 12
After applying the skip pattern (visit, skip, visit, skip...): we get [1, 3, 8, 6, 9, 11]
Solution Implementation
1class Solution:
2 def zigzagTraversal(self, grid: List[List[int]]) -> List[int]:
3 # Flag to track whether to include the current element
4 should_include = True
5 # Result list to store the zigzag traversal
6 result = []
7
8 # Iterate through each row with its index
9 for row_index, row in enumerate(grid):
10 # For odd-indexed rows, reverse the traversal direction
11 if row_index % 2 == 1:
12 row.reverse()
13
14 # Process each element in the current row
15 for element in row:
16 # Only add element if the flag indicates we should
17 if should_include:
18 result.append(element)
19 # Toggle the flag for the next element
20 should_include = not should_include
21
22 return result
23
1class Solution {
2 /**
3 * Performs a zigzag traversal of a 2D grid.
4 * For even-indexed rows (0, 2, 4...), traverse left to right.
5 * For odd-indexed rows (1, 3, 5...), traverse right to left.
6 * Additionally, only add every other element to the result.
7 *
8 * @param grid The 2D integer array to traverse
9 * @return List containing elements from the zigzag traversal
10 */
11 public List<Integer> zigzagTraversal(int[][] grid) {
12 // Flag to determine whether to include the current element
13 boolean shouldInclude = true;
14
15 // Result list to store the zigzag traversal
16 List<Integer> result = new ArrayList<>();
17
18 // Iterate through each row of the grid
19 for (int rowIndex = 0; rowIndex < grid.length; rowIndex++) {
20 // Reverse odd-indexed rows to achieve right-to-left traversal
21 if (rowIndex % 2 == 1) {
22 reverseArray(grid[rowIndex]);
23 }
24
25 // Process each element in the current row
26 for (int element : grid[rowIndex]) {
27 // Add element to result if the flag allows
28 if (shouldInclude) {
29 result.add(element);
30 }
31 // Toggle the flag for alternating selection
32 shouldInclude = !shouldInclude;
33 }
34 }
35
36 return result;
37 }
38
39 /**
40 * Reverses the elements of an integer array in-place.
41 * Uses two-pointer approach to swap elements from both ends.
42 *
43 * @param nums The array to be reversed
44 */
45 private void reverseArray(int[] nums) {
46 int left = 0;
47 int right = nums.length - 1;
48
49 // Swap elements from both ends until pointers meet
50 while (left < right) {
51 // Swap elements at left and right positions
52 int temp = nums[left];
53 nums[left] = nums[right];
54 nums[right] = temp;
55
56 // Move pointers towards center
57 left++;
58 right--;
59 }
60 }
61}
62
1class Solution {
2public:
3 vector<int> zigzagTraversal(vector<vector<int>>& grid) {
4 vector<int> result;
5 bool shouldAdd = true; // Flag to determine if current element should be added
6
7 // Process each row of the grid
8 for (int row = 0; row < grid.size(); ++row) {
9 // Reverse odd-indexed rows to achieve zigzag pattern
10 if (row % 2 != 0) {
11 reverse(grid[row].begin(), grid[row].end());
12 }
13
14 // Iterate through elements in the current row
15 for (int element : grid[row]) {
16 // Add element to result only if flag is true
17 if (shouldAdd) {
18 result.push_back(element);
19 }
20 // Toggle the flag for next element
21 shouldAdd = !shouldAdd;
22 }
23 }
24
25 return result;
26 }
27};
28
1/**
2 * Performs a zigzag traversal of a 2D grid, reversing even-indexed rows
3 * and collecting elements at alternating positions
4 * @param grid - The 2D array to traverse
5 * @returns An array containing elements collected during zigzag traversal
6 */
7function zigzagTraversal(grid: number[][]): number[] {
8 // Initialize result array to store collected elements
9 const result: number[] = [];
10
11 // Flag to track whether to include the current element
12 let shouldInclude: boolean = true;
13
14 // Iterate through each row of the grid
15 for (let rowIndex = 0; rowIndex < grid.length; rowIndex++) {
16 // Reverse odd-indexed rows (1, 3, 5, ...) for zigzag pattern
17 if (rowIndex % 2 === 1) {
18 grid[rowIndex].reverse();
19 }
20
21 // Iterate through each element in the current row
22 for (const element of grid[rowIndex]) {
23 // Add element to result if the flag indicates we should include it
24 if (shouldInclude) {
25 result.push(element);
26 }
27 // Toggle the inclusion flag for the next element
28 shouldInclude = !shouldInclude;
29 }
30 }
31
32 return result;
33}
34
Time and Space Complexity
Time Complexity: O(m × n)
, where m
is the number of rows and n
is the number of columns in the 2D array grid
.
The algorithm iterates through each row of the grid exactly once using the outer loop, which runs m
times. For each row, it processes all n
elements in that row. Even though odd-indexed rows are reversed with row.reverse()
which takes O(n)
time, and then we iterate through the row with another O(n)
operation, these are sequential operations that still result in O(n)
time per row. Therefore, the total time complexity is O(m × n)
.
Space Complexity: O(1)
(excluding the output array ans
).
The algorithm uses only a constant amount of extra space for variables like ok
, i
, and x
. The reverse()
operation modifies the row in-place and doesn't require additional space. While the answer array ans
grows to potentially store up to O(m × n)
elements, following the convention stated in the reference answer, we exclude the space consumed by the answer array from our space complexity analysis.
Learn more about how to find time and space complexity quickly.
Common Pitfalls
Pitfall: Modifying the Original Input Grid
The current solution modifies the input grid by reversing odd-indexed rows in-place using row.reverse()
. This is a destructive operation that permanently alters the input data, which can cause issues if:
- The grid needs to be reused elsewhere in the program
- The function is called multiple times on the same grid
- The input grid is immutable or shared across different parts of the application
Example of the problem:
grid = [[1, 2, 3], [4, 5, 6], [7, 8, 9]] solution = Solution() result1 = solution.zigzagTraversal(grid) # grid is now modified: [[1, 2, 3], [6, 5, 4], [7, 8, 9]] result2 = solution.zigzagTraversal(grid) # Wrong result!
Solution: Use Non-Destructive Traversal
Instead of modifying the input grid, traverse the elements without changing the original data structure:
class Solution:
def zigzagTraversal(self, grid: List[List[int]]) -> List[int]:
should_include = True
result = []
for row_index, row in enumerate(grid):
# Determine traversal direction based on row index
if row_index % 2 == 0:
# Even rows: traverse left to right
for col_index in range(len(row)):
if should_include:
result.append(row[col_index])
should_include = not should_include
else:
# Odd rows: traverse right to left
for col_index in range(len(row) - 1, -1, -1):
if should_include:
result.append(row[col_index])
should_include = not should_include
return result
Alternative solution using slicing (creates a copy):
class Solution:
def zigzagTraversal(self, grid: List[List[int]]) -> List[int]:
should_include = True
result = []
for row_index, row in enumerate(grid):
# Create a reversed copy for odd rows instead of modifying in-place
current_row = row[::-1] if row_index % 2 == 1 else row
for element in current_row:
if should_include:
result.append(element)
should_include = not should_include
return result
Both solutions maintain the same time complexity O(m × n) while preserving the input grid's integrity.
Which algorithm should you use to find a node that is close to the root of the tree?
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!