Want Help Cracking FAANG?

(Then click this)

×
Back to Question Bank

832. Flipping an Image - Leetcode Solution

Problem Description

The "Flipping an Image" problem asks you to take a binary matrix called image (a 2D array of 0s and 1s), and perform two operations on each row:

  • Flip Horizontally: Reverse the row, so the order of elements is reversed (like mirroring the row).
  • Invert: Change every 0 to 1, and every 1 to 0.

Return the resulting 2D array after performing both operations on every row. Each row must be processed independently. You are guaranteed that the input is a valid binary matrix, and you should not reuse or overwrite elements in a way that would affect other rows.

Constraints:

  • Each element in image is either 0 or 1.
  • There is only one valid output for each input.
  • Do not reuse elements in place in a way that would affect the correctness of other rows.

Thought Process

At first glance, the task seems to require two separate operations: first, reverse each row, and then invert each value. A brute-force approach would be to create a new matrix, copy each row in reverse order, and then flip each bit from 0 to 1 or 1 to 0. However, we can optimize this by noticing that both operations can be combined in a single pass.

If we process a row from both ends towards the center, we can swap and invert the values at the same time. This reduces the number of passes over the data, making the solution more efficient. It's similar to how you might reverse a word by swapping characters from both ends, but with the added twist of flipping the bits as you go.

The key insight is that flipping and inverting can be performed in-place, and for odd-length rows, the middle element just needs to be inverted once.

Solution Approach

Here’s how we can efficiently solve the problem:

  1. Iterate through each row of the matrix.
  2. Use two pointers (left and right) to traverse the row from both ends towards the center.
  3. For each pair of positions (left, right):
    • Swap the elements at left and right.
    • Invert both elements (change 0 to 1 and 1 to 0).
  4. If left == right (i.e., the middle of an odd-length row), just invert that element once.
  5. Move left forward and right backward until they meet or cross.
  6. Repeat for all rows.

We use in-place operations to save space, and handle each row independently to satisfy the problem constraints.

Example Walkthrough

Let’s walk through a sample input:

Input: image = [ [1, 1, 0], [1, 0, 1], [0, 0, 0] ]

  1. First row [1, 1, 0]:
    - Flip horizontally: [0, 1, 1]
    - Invert: [1, 0, 0]
  2. Second row [1, 0, 1]:
    - Flip horizontally: [1, 0, 1] (it's symmetric)
    - Invert: [0, 1, 0]
  3. Third row [0, 0, 0]:
    - Flip horizontally: [0, 0, 0]
    - Invert: [1, 1, 1]

Output: [ [1,0,0], [0,1,0], [1,1,1] ]

Each row is processed independently, and the final matrix is returned.

Time and Space Complexity

Brute-force approach:

  • Time Complexity: O(N * M), where N is the number of rows and M is the number of columns, since we process every element twice (once for reversing, once for inverting).
  • Space Complexity: O(N * M), if a new matrix is created for the result.
Optimized in-place approach:
  • Time Complexity: O(N * M), as each element is visited at most once during the combined flip-and-invert step.
  • Space Complexity: O(1), not counting the output (since we're modifying the input matrix in place).

The optimized approach is preferable as it reduces extra space usage.

Code Implementation

class Solution:
    def flipAndInvertImage(self, image):
        for row in image:
            left, right = 0, len(row) - 1
            while left <= right:
                # Swap and invert
                row[left], row[right] = row[right] ^ 1, row[left] ^ 1
                if left == right:
                    # If pointers meet, only invert once
                    row[left] = row[left] ^ 1
                left += 1
                right -= 1
        return image
      
class Solution {
public:
    vector<vector<int>> flipAndInvertImage(vector<vector<int>>& image) {
        for (auto& row : image) {
            int left = 0, right = row.size() - 1;
            while (left <= right) {
                if (left == right) {
                    row[left] ^= 1;
                } else {
                    int tempLeft = row[left] ^ 1;
                    int tempRight = row[right] ^ 1;
                    row[left] = tempRight;
                    row[right] = tempLeft;
                }
                left++;
                right--;
            }
        }
        return image;
    }
};
      
class Solution {
    public int[][] flipAndInvertImage(int[][] image) {
        for (int[] row : image) {
            int left = 0, right = row.length - 1;
            while (left <= right) {
                if (left == right) {
                    row[left] ^= 1;
                } else {
                    int tempLeft = row[left] ^ 1;
                    int tempRight = row[right] ^ 1;
                    row[left] = tempRight;
                    row[right] = tempLeft;
                }
                left++;
                right--;
            }
        }
        return image;
    }
}
      
var flipAndInvertImage = function(image) {
    for (let row of image) {
        let left = 0, right = row.length - 1;
        while (left <= right) {
            if (left === right) {
                row[left] ^= 1;
            } else {
                let tempLeft = row[left] ^ 1;
                let tempRight = row[right] ^ 1;
                row[left] = tempRight;
                row[right] = tempLeft;
            }
            left++;
            right--;
        }
    }
    return image;
};
      

Summary

The "Flipping an Image" problem is elegantly solved by combining the flipping and inverting steps into a single pass using a two-pointer approach. This reduces both the time and space complexity, making the solution efficient and clean. The key insight is recognizing that both operations can be performed in-place, and that each row can be processed independently. This approach demonstrates how careful observation and pointer manipulation can optimize seemingly straightforward problems.