Facebook Pixel

832. Flipping an Image

EasyBit ManipulationArrayTwo PointersMatrixSimulation
Leetcode Link

Problem Description

You are given an n x n binary matrix called image. Your task is to perform two operations on this matrix and return the result:

  1. Flip the image horizontally: This means reversing each row of the matrix. For example, if a row is [1,1,0], after flipping horizontally it becomes [0,1,1].

  2. Invert the image: After flipping, you need to invert all values in the matrix. This means changing every 0 to 1 and every 1 to 0. For example, if a row is [0,1,1] after flipping, inverting it results in [1,0,0].

The problem asks you to apply both operations in sequence (first flip, then invert) to the entire matrix and return the transformed matrix.

Example walkthrough:

  • Original row: [1,1,0]
  • After horizontal flip: [0,1,1]
  • After inversion: [1,0,0]

The solution uses a clever optimization: instead of performing the flip and invert as two separate operations, it combines them using two pointers. When comparing elements from both ends of a row, if they are equal, flipping would make them swap positions but remain equal, and then inverting would change both values - this is equivalent to just inverting both values in place. If they are different, flipping would swap them and inverting would flip their values again, resulting in no net change to their values.

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

Intuition

Let's think about what happens to each element when we flip and then invert a row. Consider any row and look at two elements: one at position i from the left and another at position j from the right (where j = n-1-i).

After flipping horizontally, these two elements swap positions. Then after inverting, both values get flipped from 0 to 1 or from 1 to 0.

Now here's the key insight: instead of actually swapping and then inverting, let's analyze what the final result would be:

Case 1: When row[i] == row[j]

  • Let's say both are 1. After flipping, they swap but remain 1 at their new positions. After inverting, both become 0.
  • If both were 0, after flipping they're still 0 at swapped positions, then after inverting both become 1.
  • Notice that in both scenarios, we end up with the opposite values at both positions. This is exactly what XOR with 1 does: value ^ 1.

Case 2: When row[i] != row[j]

  • If row[i] = 1 and row[j] = 0, after flipping they become row[i] = 0 and row[j] = 1. After inverting, they become row[i] = 1 and row[j] = 0.
  • The values remain exactly the same as before! The swap followed by inversion cancels out.

This observation leads to an elegant solution: we can use two pointers scanning from both ends toward the center. When the values are equal, we just invert both (using XOR with 1). When they're different, we do nothing. For the middle element (when n is odd), it stays in the same position after flipping, so we only need to invert it.

This way, we can transform the matrix in-place with a single pass through each row, avoiding the need to explicitly perform the flip operation.

Learn more about Two Pointers patterns.

Solution Approach

The solution implements a two-pointer technique to process each row of the matrix in-place.

Algorithm Steps:

  1. Iterate through each row of the matrix independently.

  2. For each row, initialize two pointers:

    • i starting at index 0 (leftmost element)
    • j starting at index n-1 (rightmost element)
  3. Process elements while i < j:

    • Check if row[i] == row[j]:
      • If they are equal, apply XOR with 1 to both elements: row[i] ^= 1 and row[j] ^= 1
      • This inverts both values (0 becomes 1, 1 becomes 0)
    • If they are different, do nothing (as the flip and invert operations cancel out)
    • Move pointers toward center: i += 1 and j -= 1
  4. Handle the middle element (when i == j):

    • This only occurs when the row has odd length
    • The middle element doesn't move during horizontal flip, so we only need to invert it: row[i] ^= 1
  5. Return the modified matrix after processing all rows.

Key Implementation Details:

  • The XOR operation (^=) with 1 is used for inversion: 0 ^ 1 = 1 and 1 ^ 1 = 0
  • The solution modifies the input matrix in-place, achieving O(1) space complexity
  • Time complexity is O(nΒ²) where we visit each element exactly once
  • No additional data structures are needed beyond the two pointer variables

This approach elegantly combines the flip and invert operations into a single pass, avoiding the need to explicitly reverse each row and then invert all values separately.

Ready to land your dream job?

Unlock your dream job with a 3-minute evaluator for a personalized learning plan!

Start Evaluator

Example Walkthrough

Let's walk through the solution with a small 3x3 matrix to see how the two-pointer approach works:

Input matrix:

[[1, 1, 0],
 [1, 0, 1],
 [0, 0, 0]]

Processing Row 1: [1, 1, 0]

  • Initialize pointers: i = 0 (pointing to 1), j = 2 (pointing to 0)
  • Compare row[0] and row[2]: 1 β‰  0 (different)
    • Since they're different, do nothing
    • Move pointers: i = 1, j = 1
  • Now i == j (middle element)
    • Invert row[1]: 1 ^ 1 = 0
  • Result: [1, 0, 0]

Processing Row 2: [1, 0, 1]

  • Initialize pointers: i = 0 (pointing to 1), j = 2 (pointing to 1)
  • Compare row[0] and row[2]: 1 == 1 (equal)
    • Invert both: row[0] = 1 ^ 1 = 0, row[2] = 1 ^ 1 = 0
    • Move pointers: i = 1, j = 1
  • Now i == j (middle element)
    • Invert row[1]: 0 ^ 1 = 1
  • Result: [0, 1, 0]

Processing Row 3: [0, 0, 0]

  • Initialize pointers: i = 0 (pointing to 0), j = 2 (pointing to 0)
  • Compare row[0] and row[2]: 0 == 0 (equal)
    • Invert both: row[0] = 0 ^ 1 = 1, row[2] = 0 ^ 1 = 1
    • Move pointers: i = 1, j = 1
  • Now i == j (middle element)
    • Invert row[1]: 0 ^ 1 = 1
  • Result: [1, 1, 1]

Final Output:

[[1, 0, 0],
 [0, 1, 0],
 [1, 1, 1]]

To verify this is correct, let's check row 1 using the traditional approach:

  • Original: [1, 1, 0]
  • After flip: [0, 1, 1]
  • After invert: [1, 0, 0] βœ“

The two-pointer approach achieves the same result in a single pass!

Solution Implementation

1class Solution:
2    def flipAndInvertImage(self, image: List[List[int]]) -> List[List[int]]:
3        """
4        Flips each row horizontally and inverts all bits in the image.
5      
6        Args:
7            image: 2D list of binary values (0 or 1)
8          
9        Returns:
10            Modified image after flipping and inverting
11        """
12        n = len(image)
13      
14        # Process each row in the image
15        for row in image:
16            left = 0
17            right = n - 1
18          
19            # Use two pointers to process the row from both ends
20            while left < right:
21                # If values at symmetric positions are equal,
22                # after flip they remain at same positions but need inversion
23                if row[left] == row[right]:
24                    row[left] ^= 1  # Invert left element using XOR
25                    row[right] ^= 1  # Invert right element using XOR
26                # If values are different, swapping during flip
27                # automatically gives us the inverted result
28              
29                # Move pointers toward center
30                left += 1
31                right -= 1
32          
33            # Handle middle element for odd-length rows
34            # Middle element stays in place but needs inversion
35            if left == right:
36                row[left] ^= 1
37              
38        return image
39
1class Solution {
2    public int[][] flipAndInvertImage(int[][] image) {
3        // Process each row in the image
4        for (int[] row : image) {
5            int left = 0;
6            int right = row.length - 1;
7          
8            // Use two pointers to flip and invert simultaneously
9            while (left < right) {
10                // When values at symmetric positions are equal,
11                // after flip and invert, they need to be toggled
12                if (row[left] == row[right]) {
13                    row[left] ^= 1;  // Toggle left element (0->1, 1->0)
14                    row[right] ^= 1; // Toggle right element (0->1, 1->0)
15                }
16                // When values are different, flip cancels out with invert,
17                // so no change needed
18              
19                left++;
20                right--;
21            }
22          
23            // Handle middle element for odd-length rows
24            // Middle element only needs to be inverted (no flip needed)
25            if (left == right) {
26                row[left] ^= 1;
27            }
28        }
29      
30        return image;
31    }
32}
33
1class Solution {
2public:
3    vector<vector<int>> flipAndInvertImage(vector<vector<int>>& image) {
4        // Process each row in the image
5        for (auto& row : image) {
6            int left = 0;
7            int right = row.size() - 1;
8          
9            // Use two pointers to process the row from both ends
10            while (left < right) {
11                // Key insight: After flipping and inverting:
12                // - If row[left] == row[right], both need to be inverted
13                // - If row[left] != row[right], they swap positions and get inverted,
14                //   which results in no change (swap + invert cancels out)
15                if (row[left] == row[right]) {
16                    row[left] ^= 1;  // Invert left element (0->1, 1->0)
17                    row[right] ^= 1; // Invert right element (0->1, 1->0)
18                }
19              
20                // Move pointers towards the center
21                ++left;
22                --right;
23            }
24          
25            // Handle the middle element for odd-length rows
26            // The middle element stays in place after flipping, only needs inverting
27            if (left == right) {
28                row[left] ^= 1;
29            }
30        }
31      
32        return image;
33    }
34};
35
1/**
2 * Flips and inverts a binary image
3 * @param image - 2D array representing a binary image
4 * @returns The flipped and inverted image
5 */
6function flipAndInvertImage(image: number[][]): number[][] {
7    // Process each row in the image
8    for (const row of image) {
9        let leftIndex: number = 0;
10        let rightIndex: number = row.length - 1;
11      
12        // Use two pointers to flip and invert simultaneously
13        // Move pointers towards the center
14        for (; leftIndex < rightIndex; ++leftIndex, --rightIndex) {
15            // If values at both ends are equal, we need to invert both
16            // After flipping, they would be in opposite positions
17            // Since they're equal, flipping + inverting means just inverting both
18            if (row[leftIndex] === row[rightIndex]) {
19                row[leftIndex] ^= 1;  // Invert left element using XOR
20                row[rightIndex] ^= 1; // Invert right element using XOR
21            }
22            // If values are different, flipping and then inverting
23            // results in no change needed (they cancel out)
24        }
25      
26        // Handle the middle element for odd-length rows
27        // Middle element only needs to be inverted (no flip partner)
28        if (leftIndex === rightIndex) {
29            row[leftIndex] ^= 1;
30        }
31    }
32  
33    return image;
34}
35

Time and Space Complexity

The time complexity is O(nΒ²), where n is the number of rows (or columns) in the matrix. The algorithm iterates through each of the n rows, and for each row, it processes approximately n/2 pairs of elements using the two-pointer approach (from index 0 to n-1). Even though each row only requires n/2 iterations due to the two-pointer technique, this still results in O(n) operations per row. With n rows total, the overall time complexity is O(n) Γ— O(n) = O(nΒ²).

The space complexity is O(1). The algorithm modifies the input matrix in-place without using any additional data structures that scale with the input size. Only a constant amount of extra space is used for the loop variables (i, j, and row), regardless of the matrix dimensions.

Learn more about how to find time and space complexity quickly.

Common Pitfalls

1. Incorrect Middle Element Handling

The Pitfall: A common mistake is forgetting to handle the middle element in odd-length rows or incorrectly placing the middle element logic inside the while loop.

Incorrect Implementation:

while left <= right:  # Wrong condition!
    if row[left] == row[right]:
        row[left] ^= 1
        row[right] ^= 1
    left += 1
    right -= 1

Why it's wrong: Using left <= right causes the middle element to be processed twice when left == right, inverting it twice (which cancels out the inversion).

Correct Solution:

while left < right:
    if row[left] == row[right]:
        row[left] ^= 1
        row[right] ^= 1
    left += 1
    right -= 1

# Handle middle element separately
if left == right:
    row[left] ^= 1

2. Misunderstanding the Logic When Elements Are Different

The Pitfall: Explicitly swapping elements when they're different, not realizing that doing nothing achieves the correct result.

Incorrect Implementation:

if row[left] == row[right]:
    row[left] ^= 1
    row[right] ^= 1
else:
    # Unnecessary swap and invert
    row[left], row[right] = row[right], row[left]
    row[left] ^= 1
    row[right] ^= 1

Why it's wrong: When elements are different (e.g., left=0, right=1), flipping makes them (1, 0), then inverting makes them (0, 1). This is exactly what we started with, so no operation is needed.

Correct Solution: Simply do nothing when elements are different.

3. Using Row Index Instead of Column Index

The Pitfall: Using the wrong dimension for the right pointer initialization.

Incorrect Implementation:

for row in image:
    left = 0
    right = len(image) - 1  # Wrong! This is number of rows

Correct Solution:

for row in image:
    left = 0
    right = len(row) - 1  # Correct: use row length
    # OR
    right = len(image[0]) - 1  # Since it's an n x n matrix

4. Creating New Matrix Instead of In-Place Modification

The Pitfall: Creating a new matrix when the problem can be solved in-place, leading to unnecessary space complexity.

Less Optimal:

result = []
for row in image:
    new_row = []
    for i in range(len(row) - 1, -1, -1):
        new_row.append(row[i] ^ 1)
    result.append(new_row)
return result

Better Solution: Modify the input matrix directly as shown in the original solution, achieving O(1) space complexity.

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

What's the output of running the following function using input [30, 20, 10, 100, 33, 12]?

1def fun(arr: List[int]) -> List[int]:
2    import heapq
3    heapq.heapify(arr)
4    res = []
5    for i in range(3):
6        res.append(heapq.heappop(arr))
7    return res
8
1public static int[] fun(int[] arr) {
2    int[] res = new int[3];
3    PriorityQueue<Integer> heap = new PriorityQueue<>();
4    for (int i = 0; i < arr.length; i++) {
5        heap.add(arr[i]);
6    }
7    for (int i = 0; i < 3; i++) {
8        res[i] = heap.poll();
9    }
10    return res;
11}
12
1class HeapItem {
2    constructor(item, priority = item) {
3        this.item = item;
4        this.priority = priority;
5    }
6}
7
8class MinHeap {
9    constructor() {
10        this.heap = [];
11    }
12
13    push(node) {
14        // insert the new node at the end of the heap array
15        this.heap.push(node);
16        // find the correct position for the new node
17        this.bubble_up();
18    }
19
20    bubble_up() {
21        let index = this.heap.length - 1;
22
23        while (index > 0) {
24            const element = this.heap[index];
25            const parentIndex = Math.floor((index - 1) / 2);
26            const parent = this.heap[parentIndex];
27
28            if (parent.priority <= element.priority) break;
29            // if the parent is bigger than the child then swap the parent and child
30            this.heap[index] = parent;
31            this.heap[parentIndex] = element;
32            index = parentIndex;
33        }
34    }
35
36    pop() {
37        const min = this.heap[0];
38        this.heap[0] = this.heap[this.size() - 1];
39        this.heap.pop();
40        this.bubble_down();
41        return min;
42    }
43
44    bubble_down() {
45        let index = 0;
46        let min = index;
47        const n = this.heap.length;
48
49        while (index < n) {
50            const left = 2 * index + 1;
51            const right = left + 1;
52
53            if (left < n && this.heap[left].priority < this.heap[min].priority) {
54                min = left;
55            }
56            if (right < n && this.heap[right].priority < this.heap[min].priority) {
57                min = right;
58            }
59            if (min === index) break;
60            [this.heap[min], this.heap[index]] = [this.heap[index], this.heap[min]];
61            index = min;
62        }
63    }
64
65    peek() {
66        return this.heap[0];
67    }
68
69    size() {
70        return this.heap.length;
71    }
72}
73
74function fun(arr) {
75    const heap = new MinHeap();
76    for (const x of arr) {
77        heap.push(new HeapItem(x));
78    }
79    const res = [];
80    for (let i = 0; i < 3; i++) {
81        res.push(heap.pop().item);
82    }
83    return res;
84}
85

Recommended Readings

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

Load More