Facebook Pixel

3417. Zigzag Grid Traversal With Skip

EasyArrayMatrixSimulation
Leetcode Link

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.

Quick Interview Experience
Help others by sharing your interview experience
Have you seen this problem before?

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:

  1. Process rows in order, reversing odd-indexed rows to simulate the zigzag
  2. 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 is True - if so, add the element to our answer
  • Toggle ok using ok = 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 Evaluator

Example 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.

Discover Your Strengths and Weaknesses: Take Our 5-Minute Quiz to Tailor Your Study Plan:

Which algorithm should you use to find a node that is close to the root of the tree?


Recommended Readings

Want a Structured Path to Master System Design Too? Don’t Miss This!

Load More