最少翻转次数使二进制矩阵回文II

2024-11-16

题目:

给你一个 m x n 的二进制矩阵 grid

如果矩阵中一行或者一列从前往后与从后往前读是一样的,那么我们称这一行或者这一列是 回文 的。

你可以将 grid 中任意格子的值 翻转 ,也就是将格子里的值从 0 变成 1 ,或者从 1 变成 0

请你返回 最少 翻转次数,使得矩阵中 所有 行和列都是 回文的 ,且矩阵中 1 的数目可以被 4 整除

示例 1:

输入:grid = [[1,0,0],[0,1,0],[0,0,1]]

输出:3

解释:

img

示例 2:

输入:grid = [[0,1],[0,1],[0,0]]

输出:2

解释:

img

示例 3:

输入:grid = [[1],[1]]

输出:2

解释:

img

提示:

  • m == grid.length
  • n == grid[i].length
  • 1 <= m * n <= 2 * 105
  • 0 <= grid[i][j] <= 1

思路:

分析:行和列都是回文
为方便描述,把 grid 简称为 a。

由于所有行和列都必须是回文的,所以要满足
	a[i][j] = a[i][n−1−j] = a[m−1−i][j] = a[m−1−i][n−1−j]
也就是这四个数要么都是 0,要么都是 1。其中 0 ≤ i ≤ ⌊m/2⌋, 0 ≤ j ≤ ⌊n/2⌋。
设 cnt1 = a[i][j] + a[i][n−1−j] + a[m−1−i][j] + a[m−1−i][n−1−j]
把这四个数都变成 0 需要翻转 cnt1 次,都变成 1 需要翻转 4 − cnt1 次。
两种情况取最小值,把 min(cnt1, 4 − cnt1) 加入答案。

分析:1 的数目被 4 整除
分类讨论:
    如果 m 和 n 都是偶数:由于上面四个数四个数一组,都翻转成了 0 或者 1,所以 1 的数目能被 4 整除自动成立。
    如果 m 是奇数,n 是偶数:正中间一排需要翻转成回文的,且 1 的数目需要能被 4 整除。
    如果 m 是偶数,n 是奇数:正中间一列需要翻转成回文的,且 1 的数目需要能被 4 整除。
    如果 m 和 n 都是奇数:正中间一排和正中间一列都需要翻转成回文的,且 1 的数目需要能被 4 整除。除了正中央的格子以外,每个格子都有镜像位置,这些格子两个数两个数一组,都翻转成了 0 或者 1。那么翻转之后,不考虑正中央的格子,1 的数目就是偶数。所以正中央的格子必须是 0,否则最终 1 的数目是奇数,无法被 4 整除。
如何处理正中间一排和正中间一列,是本题的重点。

具体计算方法
首先,如果 m 和 n 都是奇数,那么正中央的格子 (⌊m/2⌋, ⌊n/2⌋) 必须是 0,把其元素值加入答案。
然后统计正中间一排(如果 m 是奇数)和正中间一列(如果 n 是奇数)的格子:
    设 diff 为镜像位置不同的数对个数。注意统计的是数对。
    设 cnt1 为镜像位置相同的 1 的个数。注意统计的是个数。

这 diff 对 0 和 1 必须翻转其中一个数,所以答案至少要增加 diff。什么情况下,可以只增加 diff?
分类讨论:
    如果 cnt1 是 4 的倍数,那么只需把这 diff 对 0 和 1 中的 1 全部变成 0,这样 1 的个数就是 4 的倍数了。所以把 diff 加入答案。
    如果 cnt1 不是 4 的倍数,由于 cnt1 是偶数,其除以 4 必定余 2(注意这说明 cnt1 ≥ 2)。继续讨论:
        如果 diff > 0,把其中一对数都变成 1,其余 diff−1 对数全部变成 0,这样 1 的个数就是 4 的倍数了。所以同样地,把 diff 加入答案。
        如果 diff = 0,我们只能把 cnt1 中的两个 1 变成 0,使得 1 的个数是 4 的倍数。所以把答案增加 2。

综上所述:
    如果 diff > 0,额外把 diff 加入答案。
    如果 diff = 0,额外把 cnt1 mod 4 加入答案。

代码:

class Solution {
public:
    int minFlips(vector<vector<int>>& a) {
        int m = a.size(), n = a[0].size();
        int ans = 0;
        for (int i = 0; i < m / 2; i++) {
            for (int j = 0; j < n / 2; j++) {
                int cnt1 = a[i][j] + a[i][n - 1 - j] + a[m - 1 - i][j] + a[m - 1 - i][n - 1 - j];
                ans += min(cnt1, 4 - cnt1); // 全为 1 或全为 0
            }
        }

        if (m % 2 && n % 2) {
            // 正中间的数必须是 0
            ans += a[m / 2][n / 2];
        }

        int diff = 0, cnt1 = 0;
        if (m % 2) {
            // 统计正中间这一排
            for (int j = 0; j < n / 2; j++) {
                if (a[m / 2][j] != a[m / 2][n - 1 - j]) {
                    diff++;
                } else {
                    cnt1 += a[m / 2][j] * 2;
                }
            }
        }
        if (n % 2) {
            // 统计正中间这一列
            for (int i = 0; i < m / 2; i++) {
                if (a[i][n / 2] != a[m - 1 - i][n / 2]) {
                    diff++;
                } else {
                    cnt1 += a[i][n / 2] * 2;
                }
            }
        }

        return ans + (diff ? diff : cnt1 % 4);
    }
};