LeetCode 861. 翻转矩阵后的得分
题目描述


题意分析
每次可以选择整行或整列,把其中的 0、1 互换。将每行从左到右看作高位到低位的二进制数,求任意次操作后所有行数值之和的最大值。同一行或列翻转两次等于不翻,只需考虑最终是否翻转。
解法:位权贪心 + 逻辑翻转
核心思路
[!blue]
第一列的位权是
2^(n - 1),其余列在同一行中的位权之和只有2^(n - 1) - 1。若某行最终首位为 0,再翻转这一整行,即使低位全部损失,最高位的收益仍然更多。因此最优结果的每一行首位都必须为 1。可以约定第一列不做列翻转,再把原首位为 0 的行翻转。这个约定不会缩小最优方案范围:若某个方案翻转了第一列,把它的所有行翻转标记和所有列翻转标记一起取反,相当于每个格子额外翻转两次,最终矩阵不变,而第一列的列翻转已被取消。此时为保证首位为 1,各行的翻转选择就由原首位唯一确定。
行翻转确定后,剩余每列互不影响。设第
j列在行翻转后有ones个 1,不翻列贡献ones * 2^(n - 1 - j),翻列则有m - ones个 1。只需选择 1 更多的方案,分别最大化每一列的贡献,合起来就是最大总分。代码不必真的修改矩阵。原首位为 0 时令
rowFlip = 1,否则为 0,用grid[i][j] ^ rowFlip得到该格在行翻转后的值。第一列已经全为 1,可以直接计入m * 2^(n - 1),其余列再逐个统计。
解题步骤
- 将首列全一的贡献计入初始答案。
- 逐个处理剩余列,按原首位推算行翻转后的格值。
- 比较翻列前后
1的数量,取较多者。- 乘当前列位权并累加。
某列的 0、1 数量相同,翻或不翻贡献一样。只有一列时,初始化已把全部格子变成 1 对应的得分计入,循环无需执行。
代码实现
class Solution {
public int matrixScore(int[][] grid) {
int m = grid.length;
int n = grid[0].length;
int answer = m * (1 << (n - 1));
for (int j = 1; j < n; j++) {
int ones = 0;
for (int i = 0; i < m; i++) {
// 行翻转由首位决定,同一行所有格子使用相同翻转标记。
int rowFlip = grid[i][0] == 0 ? 1 : 0;
ones += grid[i][j] ^ rowFlip;
}
// 当前列可翻或不翻,选择一的数量更多的一种。
int best = Math.max(ones, m - ones);
answer += best * (1 << (n - 1 - j));
}
return answer;
}
}
func matrixScore(grid [][]int) int {
m := len(grid)
n := len(grid[0])
answer := m * (1 << (n - 1))
for j := 1; j < n; j++ {
ones := 0
for i := 0; i < m; i++ {
// 行翻转由首位决定,同一行所有格子使用相同翻转标记。
rowFlip := 0
if grid[i][0] == 0 {
rowFlip = 1
}
ones += grid[i][j] ^ rowFlip
}
// 当前列可翻或不翻,选择一的数量更多的一种。
best := ones
if m-ones > best {
best = m - ones
}
answer += best * (1 << (n - 1 - j))
}
return answer
}
复杂度分析
- 时间复杂度:$O(mn)$。
- 空间复杂度:$O(1)$,只计算逻辑翻转,不修改输入矩阵。
关键点总结
[!green]
- 最高位优先由位权大小严格保证。
- 列翻转只改变本列贡献,可以独立选多数。
- 行翻转标记由首位决定,在同一行内保持一致。
易错点总结
[!yellow]
- 忽略行翻转,直接统计原列中一的个数:后续列的有效格值不正确。
- 把位权写成从左到右递增:高低位含义反了。
- 已经计入首列,又在循环里重复累计:最高位被算两次。
- 只取 ones,不考虑 m-ones:漏掉翻列可以增加得分的情况。
相似题目
| 题目 | 难度 | 关联与区别 |
|---|---|---|
| 1072. 按列翻转得到最大值等行数 | 中等 | 同样翻转整列,原题最大化行内全相等的行数,本题还可翻行且按二进制位权最大化总分。 |
转载与许可
许可
本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议,转载请注明出处!