Minimum Number of Flips to Make Binary Grid Palindromic II
MedPrompt
You are given an m x n binary matrix grid.
A row or column is considered palindromic if its values read the same forward and backward.
You can flip any number of cells in grid from 0 to 1, or from 1 to 0.
Return the minimum number of cells that need to be flipped to make all rows and columns palindromic, and the total number of 1's in grid divisible by 4.
Example 1:
Input: grid = [[1,0,0],[0,1,0],[0,0,1]]
Output: 3
Explanation:

Example 2:
Input: grid = [[0,1],[0,1],[0,0]]
Output: 2
Explanation:

Example 3:
Input: grid = [[1],[1]]
Output: 2
Explanation:

Constraints:
m == grid.lengthn == grid[i].length1 <= m * n <= 2 * 1050 <= grid[i][j] <= 1
Approaches
2 approaches with complexity analysis and trade-offs.
This approach uses dynamic programming to solve the problem by keeping track of the exact number of ones accumulated. While it correctly models the problem, its state space is very large, making it inefficient for the given constraints.
Algorithm
- Symmetric Group Decomposition:
- The first step is to realize that for the grid to be palindromic across all rows and columns, certain cells must have the same value. These cells form symmetric groups.
- For any cell
(i, j), the cells(i, n-1-j),(m-1-i, j), and(m-1-i, n-1-j)must all have the same value. These four cells form a symmetric group of size 4. - If a row or column is in the middle (i.e.,
mornis odd), the groups might be of size 2 or 1. - Iterate through the top-left quadrant of the grid to identify all unique symmetric groups and their original values.
- Cost Calculation:
- For each group, calculate the cost to make all its cells 0 (
cost0, which is the number of 1s in the group) and the cost to make all its cells 1 (cost1, which is the number of 0s in the group).
- For each group, calculate the cost to make all its cells 0 (
- Dynamic Programming:
- Define a DP state
dp[i][j]representing the minimum number of flips required using the firstigroups to achieve a total of exactlyjones in the final grid. - The DP table size will be
(number of groups + 1) x (m*n + 1). - Initialize
dp[0][0] = 0and all other entries to infinity. - The transition for group
i(with sizek_i, costscost0_i,cost1_i) is:dp[i][j] = min(dp[i-1][j] + cost0_i, dp[i-1][j - k_i] + cost1_i) - This can be space-optimized to use a 1D DP array
dp[j]of sizem*n + 1.
- Define a DP state
- Final Result:
- After iterating through all groups, the DP table
dp[j]will hold the minimum flips to getjones. - The final answer is the minimum value among
dp[j]for alljthat are divisible by 4.
- After iterating through all groups, the DP table
Walkthrough
The core idea is to break down the problem into smaller, independent subproblems. The palindromic constraints force certain cells to be equal. We can group these cells together. For each group, we have two choices: make all cells 0 or make all cells 1. This problem can be framed as a variation of the knapsack problem.
We define a DP state dp[j] as the minimum flips to achieve exactly j ones. We iterate through each symmetric group and update the DP table based on the two choices for that group.
For a group of size k with costs cost0 and cost1:
- To make it all 0s, we add
cost0flips and 0 ones. - To make it all 1s, we add
cost1flips andkones.
We build up the dp table group by group. After considering all groups, we look for the minimum flips among all states dp[j] where j is a multiple of 4.
// This is a conceptual implementation and will likely time out.class Solution { public int minFlips(int[][] grid) { int m = grid.length; int n = grid[0].length; List<int[]> groups = new ArrayList<>(); boolean[][] visited = new boolean[m][n]; for (int i = 0; i < m; i++) { for (int j = 0; j < n; j++) { if (visited[i][j]) continue; List<int[]> cells = new ArrayList<>(); cells.add(new int[]{i, j}); visited[i][j] = true; int r1 = i, c1 = n - 1 - j; if (!visited[r1][c1]) { cells.add(new int[]{r1, c1}); visited[r1][c1] = true; } int r2 = m - 1 - i, c2 = j; if (!visited[r2][c2]) { cells.add(new int[]{r2, c2}); visited[r2][c2] = true; } int r3 = m - 1 - i, c3 = n - 1 - j; if (!visited[r3][c3]) { cells.add(new int[]{r3, c3}); visited[r3][c3] = true; } int ones = 0; for (int[] cell : cells) { if (grid[cell[0]][cell[1]] == 1) { ones++; } } int size = cells.size(); int zeros = size - ones; groups.add(new int[]{size, ones, zeros}); // {k, cost0, cost1} } } int[] dp = new int[m * n + 1]; Arrays.fill(dp, Integer.MAX_VALUE); dp[0] = 0; for (int[] group : groups) { int k = group[0]; int cost0 = group[1]; int cost1 = group[2]; int[] newDp = new int[m * n + 1]; Arrays.fill(newDp, Integer.MAX_VALUE); for (int j = 0; j <= m * n; j++) { if (dp[j] != Integer.MAX_VALUE) { // Option 1: Make group all 0s newDp[j] = Math.min(newDp[j], dp[j] + cost0); // Option 2: Make group all 1s if (j + k <= m * n) { newDp[j + k] = Math.min(newDp[j + k], dp[j] + cost1); } } } dp = newDp; } int minFlips = Integer.MAX_VALUE; for (int j = 0; j <= m * n; j += 4) { minFlips = Math.min(minFlips, dp[j]); } return minFlips; }}Complexity
Time
O(G * S), where G is the number of symmetric groups (at most m*n) and S is the maximum number of ones (m*n). This leads to a complexity of O((m*n)^2).
Space
O(m*n) to store the DP table. The number of groups is also proportional to m*n, but they can be processed on the fly.
Trade-offs
Pros
It is a direct and conceptually straightforward application of dynamic programming.
It correctly solves the problem for smaller grid sizes.
Cons
The time complexity of O((m*n)^2) is too high for the given constraints and will result in a Time Limit Exceeded error.
The space complexity of O(m*n) can be very large, potentially causing memory issues for the largest grids.
This approach significantly optimizes the dynamic programming solution by reducing the state space. Instead of tracking the exact count of ones, we only track its remainder when divided by 4. This is sufficient because the problem only requires the total number of ones to be divisible by 4. This optimization reduces the time and space complexity to be linear in the size of the grid, making it highly efficient and capable of passing within the given constraints.
Algorithm
- Symmetric Group Decomposition:
- As in the previous approach, decompose the grid into symmetric groups of size
k(which can be 1, 2, or 4). This is done by iterating through the top-left quadrant of the grid.
- As in the previous approach, decompose the grid into symmetric groups of size
- Cost Calculation:
- For each group, calculate
cost0(flips to make all 0s) andcost1(flips to make all 1s).
- For each group, calculate
- Optimized Dynamic Programming:
- The key insight is that we only care about the total number of ones modulo 4. So, the DP state can be reduced.
- Define
dp[rem]as the minimum flips to achieve a total number of ones congruent toremmodulo 4. - The DP array
dpwill have a constant size of 4. - Initialize
dp[0] = 0anddp[1],dp[2],dp[3]to infinity. - Iterate through each symmetric group (
k,cost0,cost1). - In each iteration, compute a
new_dparray based on the currentdparray and the two choices for the current group:- Choice 1 (make all 0s): Adds
cost0to flips, number of ones' remainder does not change. - Choice 2 (make all 1s): Adds
cost1to flips, number of ones' remainder changes byk % 4.
- Choice 1 (make all 0s): Adds
- Update
new_dp[rem]andnew_dp[(rem + k) % 4]for eachremfrom 0 to 3. - After processing the group, replace
dpwithnew_dp.
- Final Result:
- After iterating through all groups, the value
dp[0]will hold the minimum flips to make the total number of ones divisible by 4.
- After iterating through all groups, the value
Walkthrough
We start by identifying the same symmetric groups of cells as in the naive approach. For each group, we have two choices: make all its cells 0 or all 1. This choice affects the total flip count and the total number of ones.
The crucial observation is that the final condition on the number of ones, total_ones % 4 == 0, only depends on the sum of ones from each group modulo 4. This allows us to define a much smaller DP state: dp[rem], the minimum flips needed to get a total number of ones congruent to rem (where rem is 0, 1, 2, or 3).
The DP table is an array of size 4. We process each symmetric group one by one, updating the DP table at each step. For a group of size k, if we choose to make it all 1s, the remainder of total ones will change from rem to (rem + k) % 4. If we make it all 0s, the remainder stays rem. We always choose the option that minimizes the flips for each possible resulting remainder.
After considering all groups, dp[0] will contain the minimum flips to satisfy all conditions.
class Solution { public int minFlips(int[][] grid) { int m = grid.length; int n = grid[0].length; long[] dp = new long[4]; Arrays.fill(dp, Long.MAX_VALUE); dp[0] = 0; boolean[][] visited = new boolean[m][n]; for (int i = 0; i < m; i++) { for (int j = 0; j < n; j++) { if (visited[i][j]) { continue; } List<Integer> values = new ArrayList<>(); int[] p1 = {i, j}; int[] p2 = {i, n - 1 - j}; int[] p3 = {m - 1 - i, j}; int[] p4 = {m - 1 - i, n - 1 - j}; if (!visited[p1[0]][p1[1]]) { values.add(grid[p1[0]][p1[1]]); visited[p1[0]][p1[1]] = true; } if (!visited[p2[0]][p2[1]]) { values.add(grid[p2[0]][p2[1]]); visited[p2[0]][p2[1]] = true; } if (!visited[p3[0]][p3[1]]) { values.add(grid[p3[0]][p3[1]]); visited[p3[0]][p3[1]] = true; } if (!visited[p4[0]][p4[1]]) { values.add(grid[p4[0]][p4[1]]); visited[p4[0]][p4[1]] = true; } int k = values.size(); int ones = 0; for (int val : values) { if (val == 1) { ones++; } } int zeros = k - ones; int cost0 = ones; // Flips to make all 0s int cost1 = zeros; // Flips to make all 1s dp = updateDp(dp, k, cost0, cost1); } } return (int) dp[0]; } private long[] updateDp(long[] dp, int k, int cost0, int cost1) { long[] newDp = new long[4]; Arrays.fill(newDp, Long.MAX_VALUE); for (int rem = 0; rem < 4; rem++) { if (dp[rem] != Long.MAX_VALUE) { // Choice 1: Make group all 0s int newRem0 = rem; long newFlips0 = dp[rem] + cost0; newDp[newRem0] = Math.min(newDp[newRem0], newFlips0); // Choice 2: Make group all 1s int newRem1 = (rem + k) % 4; long newFlips1 = dp[rem] + cost1; newDp[newRem1] = Math.min(newDp[newRem1], newFlips1); } } return newDp; }}Complexity
Time
O(m*n). We iterate through each cell of the grid once to form and process the symmetric groups. The DP update for each group takes constant time (O(4)).
Space
O(1), as the DP table size is constant (4), and the visited grid can be avoided by iterating smartly over the top-left quadrant.
Trade-offs
Pros
Extremely efficient, with time complexity linear in the number of cells.
Optimal space complexity, using only a constant amount of extra space for the DP table.
It is the standard and best way to solve this type of knapsack-like problem with modular constraints.
Cons
The logic involving modulo arithmetic can be slightly more complex to grasp initially compared to the naive approach.
Solutions
Solution
class Solution {public int minFlips(int[][] grid) { int m = grid.length, n = grid[0].length; int ans = 0; for (int i = 0; i < m / 2; ++i) { for (int j = 0; j < n / 2; ++j) { int x = m - i - 1, y = n - j - 1; int cnt1 = grid[i][j] + grid[x][j] + grid[i][y] + grid[x][y]; ans += Math.min(cnt1, 4 - cnt1); } } if (m % 2 == 1 && n % 2 == 1) { ans += grid[m / 2][n / 2]; } int diff = 0, cnt1 = 0; if (m % 2 == 1) { for (int j = 0; j < n / 2; ++j) { if (grid[m / 2][j] == grid[m / 2][n - j - 1]) { cnt1 += grid[m / 2][j] * 2; } else { diff += 1; } } } if (n % 2 == 1) { for (int i = 0; i < m / 2; ++i) { if (grid[i][n / 2] == grid[m - i - 1][n / 2]) { cnt1 += grid[i][n / 2] * 2; } else { diff += 1; } } } ans += cnt1 % 4 == 0 || diff > 0 ? diff : 2; return ans; }}class Solution {public: int minFlips(vector<vector<int>> &grid) { int m = grid.size(), n = grid[0].size(); int ans = 0; for (int i = 0; i < m / 2; ++i) { for (int j = 0; j < n / 2; ++j) { int x = m - i - 1, y = n - j - 1; int cnt1 = grid[i][j] + grid[x][j] + grid[i][y] + grid[x][y]; ans += min(cnt1, 4 - cnt1); } } if (m % 2 == 1 && n % 2 == 1) { ans += grid[m / 2][n / 2]; } int diff = 0, cnt1 = 0; if (m % 2 == 1) { for (int j = 0; j < n / 2; ++j) { if (grid[m / 2][j] == grid[m / 2][n - j - 1]) { cnt1 += grid[m / 2][j] * 2; } else { diff += 1; } } } if (n % 2 == 1) { for (int i = 0; i < m / 2; ++i) { if (grid[i][n / 2] == grid[m - i - 1][n / 2]) { cnt1 += grid[i][n / 2] * 2; } else { diff += 1; } } } ans += cnt1 % 4 == 0 || diff > 0 ? diff : 2; return ans; }};class Solution: def minFlips(self, grid: List[List[int]]) -> int: m, n = len(grid), len(grid[0]) ans = 0 for i in range(m // 2): for j in range(n // 2): x, y = m - i - 1, n - j - 1 cnt1 = grid[i][j] + grid[x][j] + grid[i][y] + grid[x][y] ans += min(cnt1, 4 - cnt1) if m % 2 and n % 2: ans += grid[m // 2][n // 2] diff = cnt1 = 0 if m % 2: for j in range(n // 2): if grid[m // 2][j] == grid[m // 2][n - j - 1]: cnt1 += grid[m // 2][j] * 2 else: diff += 1 if n % 2: for i in range(m // 2): if grid[i][n // 2] == grid[m - i - 1][n // 2]: cnt1 += grid[i][n // 2] * 2 else: diff += 1 ans += diff if cnt1 % 4 == 0 or diff else 2 return ansVideo walkthrough
Newsletter
One sharp idea, every week
System design and interview prep — short enough to finish.
No spam. Unsubscribe anytime.
Practice
Same difficulty — related problems to reinforce the pattern.