📌  相关文章
📜  翻转二进制矩阵最多K次后的最高分数(1)

📅  最后修改于: 2023-12-03 15:27:41.513000             🧑  作者: Mango

翻转二进制矩阵最多K次后的最高分数

1. 问题描述

给定一个 $m\times n$ 的二进制矩阵,你可以将其中的一个位置从 0 变成 1 也可以将其中的一个位置从 1 变成 0。每做一次操作,可以将周围4个位置(上下左右)也一同变换。你需要在翻转最多 K 次之后,使得矩阵中的 1 的个数最多。

2. 解法
2.1 二分答案 + DFS

本道题最先想到的是通过二分答案 $r$。那么问题就转化成了:是否存在一种方案,可以将矩阵中的所有 0,都变成了 1,且最多翻转 $r$ 次(这里翻转 $x$ 次表示最多翻转 $x$ 次)?

对于判断答案是否可行,可以通过 DFS 遍历矩阵,将一个位置从 0 变成 1 或从 1 变成 0。由于每次需要一起翻转周围 4 个位置,因此可以利用 bfs 遍历这 4 个位置进行翻转。最终将矩阵中的 0 全部变成 1 后,与 $r$ 进行比较。如果 $r$ 小于等于已经翻转的次数,说明当前答案可行。

当 $r$ 大于可行的答案时,二分 $r$ 值:如果不存在一种方案,可以将所有 0 转换成 1,需要翻转的次数小于 $mid$,则更新答案,并将 $r$ 下界更新为 $mid+1$;否则将上界更新为 $mid-1$。

时间复杂度为 $O(\log C \times n^2)$,其中 $C$ 为矩阵中的 0 的数量。

2.2 二进制枚举 + 贪心

当数字范围较小时,可以尝试使用位运算进行解决。我们可以枚举一个数 $s$,其中 $s$ 的二进制表示中不超过 $k$ 个位置为 1,表示翻转这些位置以后的操作可以将 0 变成 1。

对于每一种翻转方案 $s$,可以通过贪心的方式考虑最终翻转的位置。每次将所有可以翻转的位置按照当前方案 $s$ 的二进制表示中的位置进行排序,从最大的位置开始翻转。如果每次翻转完之后,1 的数量仍然增加,则说明这种方案可行。

时间复杂度为 $O(2^n\times n^2\times \log n)$,其中 $n$ 表示矩阵的长度。

3. 代码
3.1 二分答案 + DFS
from collections import deque

class Solution:
    def maxCount(self, mat: List[List[int]], k: int) -> int:
        m, n = len(mat), len(mat[0])

        def dfs(i, j, visited, cnt):
            if i < 0 or i >= m or j < 0 or j >= n or visited[i][j] or cnt > k:
                return 0
            visited[i][j] = True
            res = 0
            for dx, dy in [[1, 0], [-1, 0], [0, 1], [0, -1]]:
                x, y = i+dx, j+dy
                if 0 <= x < m and 0 <= y < n:
                    res += dfs(x, y, visited, cnt+mat[x][y]^1)
            visited[i][j] = False
            return res + mat[i][j]

        l, r, ans = 0, m*n, 0
        while l <= r:
            mid = (l + r) // 2
            flag = False
            for i in range(m):
                for j in range(n):
                    cnt = dfs(i, j, [[False for _ in range(n)] for _ in range(m)], 0)
                    if cnt <= mid:
                        ans = max(ans, cnt + sum([mat[x][y]^1 for x in range(m) for y in range(n)]))
                        flag = True
            if flag:
                l = mid + 1
            else:
                r = mid - 1
        return ans
3.2 二进制枚举 + 贪心
class Solution:
    def maxCount(self, mat: List[List[int]], k: int) -> int:
        m, n = len(mat), len(mat[0])
        ans = 0
        for s in range(1 << n):
            if bin(s).count('1') > k:
                continue
            cur = [mat[i].copy() for i in range(m)]
            for j in range(n):
                if (1 << n-1-j) & s:
                    for i in range(m):
                        cur[i][j] ^= 1
            cnt = sum([cur[i].count(1) for i in range(m)])
            for j in range(n):
                cc = sum([cur[i][j] for i in range(m)])
                if cc < m - cc:
                    cnt += m-2*cc
            ans = max(ans, cnt)
        return ans