翻转矩阵后的得分 图解题解
这道题到底在问什么
- 输入
- grid=[[0,0,1,1],[1,0,1,0],[1,1,0,0]]
- 输出
- 39
- 输入
- 最优翻法后
- 输出
- 1111 + 1001 + 1111 = 15 + 9 + 15 = 39
最优解:一步一步想明白
- 3记牢这把尺子:首列必须全 1(最高位优先),其余每列让 1 尽量多——1 少于 0 才翻、相等不动,最终 1 不少于 0。下面每一帧都在套它。
- 4原矩阵 3 行 4 列,每格非 0 即 1这是原始矩阵,绿色格是 1、蓝色格是 0。我们先处理行、再处理列。先想清楚:每行的最左边是最高位,一个高位的 1 抵得上后面好几位,所以最该先抢的就是首列。
- 5只要某行首位是 0,就翻整行,把最高位抢成 1紫色这一列是首列,也就是每行的最高位。我们一行一行检查它的首位:是 1 就不动,是 0 就把整行翻过来。先从第 0 行开始。
- 6第 0 行 = [0, 0, 1, 1],首位是 0轮到第 0 行,内容是 [0, 0, 1, 1]。它的首位是 0。首位是 0,最高位还没抢到手,这一整行得翻一次。
- 7第 0 行现在 = [1, 1, 0, 0],首位 = 1整行翻过来(红色闪一下),第 0 行变成 [1, 1, 0, 0],首位现在是 1,最高位拿下了。
- 8第 1 行 = [1, 0, 1, 0],首位是 1轮到第 1 行,内容是 [1, 0, 1, 0]。它的首位是 1。首位已经是 1,最高位到手,这一行不用动。
- 9第 1 行现在 = [1, 0, 1, 0],首位 = 1第 1 行原样保留,首位本来就是 1,白翻还会把它弄丢,不动它。
- 10第 2 行 = [1, 1, 0, 0],首位是 1轮到第 2 行,内容是 [1, 1, 0, 0]。它的首位是 1。首位已经是 1,最高位到手,这一行不用动。
- 11第 2 行现在 = [1, 1, 0, 0],首位 = 1第 2 行原样保留,首位本来就是 1,白翻还会把它弄丢,不动它。
- 12三行处理完,首列三个格全部为 1第一步收工。看紫色的首列,现在三个格全是 1,每一行的最高位都抢到了手。矩阵变成 [1,1,0,0]、[1,0,1,0]、[1,1,0,0]。接下来处理其余各列,让低位也尽量多挣分。
- 13某列 1 少于 0,就翻整列让 1 变多(相等不翻)第二步换个方向,逐列看。每一列数一数有几个 1,如果 1 的个数少于 0,就把整列翻过来;相等就不翻,目标是让每列 1 不少于 0。注意首列已经全是 1 了,翻它只会变差,所以从第 1 列起真正可能动手。
- 14第 0 列 = [1, 1, 1],1 有 3 个、0 有 0 个看第 0 列(紫色),从上到下是 [1, 1, 1]。数一下:1 有 3 个,0 有 0 个。这是首列,刚抢成全 1,自然不用翻。
- 15第 0 列现在 = [1, 1, 1],1 有 3 个第 0 列保持不动,1 本来就有 3 个、不少于 0,翻了反而变少。
- 16第 1 列 = [1, 0, 1],1 有 2 个、0 有 1 个看第 1 列(紫色),从上到下是 [1, 0, 1]。数一下:1 有 2 个,0 有 1 个。1 不少于 0,保持不动。
- 17第 1 列现在 = [1, 0, 1],1 有 2 个第 1 列保持不动,1 本来就有 2 个、不少于 0,翻了反而变少。
- 18第 2 列 = [0, 1, 0],1 有 1 个、0 有 2 个看第 2 列(紫色),从上到下是 [0, 1, 0]。数一下:1 有 1 个,0 有 2 个。1 少于 0,这一列翻过来更划算。
- 19第 2 列现在 = [1, 0, 1],1 有 2 个整列翻过来(红色闪一下),第 2 列变成 [1, 0, 1],现在 1 有 2 个,1 不少于 0 了。
- 20第 3 列 = [0, 0, 0],1 有 0 个、0 有 3 个看第 3 列(紫色),从上到下是 [0, 0, 0]。数一下:1 有 0 个,0 有 3 个。1 少于 0,这一列翻过来更划算。
- 21第 3 列现在 = [1, 1, 1],1 有 3 个整列翻过来(红色闪一下),第 3 列变成 [1, 1, 1],现在 1 有 3 个,1 不少于 0 了。
- 22四列都处理完,矩阵 = [1, 1, 1, 1] / [1, 0, 0, 1] / [1, 1, 1, 1]第二步收工,矩阵不再翻动了。现在每一行是 [1111]、[1001]、[1111]。最后一步,把每一行当成二进制数算出来,再加到一起。
- 23第 0 行 1111 的十进制值是 15把第 0 行(紫色)按二进制读:1111,换成十进制就是 15。累计得分加上它,现在是 15。
- 24第 1 行 1001 的十进制值是 9把第 1 行(紫色)按二进制读:1001,换成十进制就是 9。累计得分加上它,现在是 24。
- 25第 2 行 1111 的十进制值是 15把第 2 行(紫色)按二进制读:1111,换成十进制就是 15。累计得分加上它,现在是 39。
- 26三行求和 15 + 9 + 15 = 39三行加起来:15 加 9 加 15,等于 39。这就是最大得分。先翻行保最高位、再翻列让每列 1 不少于 0,这套贪心一定最优。
⚠️ 容易写错的地方
✗ 错:只顾让总的 1 最多,不优先保首列
✓ 对:先把每行首位抢成 1,再管其余列
一个最高位的 1 比它后面所有位加起来还大,贪心必须最高位优先,不能为凑别处的 1 而牺牲首位
✗ 错:翻完行后又回头去翻行
✓ 对:行只在第一步处理一遍,之后只翻列
首列已经全 1,再翻任何一行都会破坏最高位、得不偿失;两步顺序固定不可逆
✗ 错:判断列要不要翻时把等号方向搞反
✓ 对:1 少于 0 才翻,相等不翻,最终 1 不少于 0
目标是让 1 尽量多,所以只在 1 少于 0 时翻;相等或 1 已不少于 0 都不该动
完整代码(Python / C++ / Java)
Python
from __future__ import annotations
from typing import *
from collections import *
from functools import *
from itertools import *
from math import *
from heapq import *
from bisect import *
class Solution:
def matrixScore(self, grid: List[List[int]]) -> int:
m, n = len(grid), len(grid[0])
for i in range(m):
if grid[i][0] == 0:
for j in range(n):
grid[i][j] ^= 1
ans = 0
for j in range(n):
cnt = sum(grid[i][j] for i in range(m))
ans += max(cnt, m - cnt) * (1 << (n - j - 1))
return ansC++
#include <algorithm>
#include <array>
#include <cctype>
#include <climits>
#include <cmath>
#include <deque>
#include <functional>
#include <iostream>
#include <limits>
#include <map>
#include <numeric>
#include <queue>
#include <set>
#include <sstream>
#include <stack>
#include <string>
#include <tuple>
#include <unordered_map>
#include <unordered_set>
#include <utility>
#include <vector>
using namespace std;
class Solution {
public:
int matrixScore(vector<vector<int>>& grid) {
int m = grid.size(), n = grid[0].size();
for (int i = 0; i < m; ++i) {
if (grid[i][0] == 0) {
for (int j = 0; j < n; ++j) {
grid[i][j] ^= 1;
}
}
}
int ans = 0;
for (int j = 0; j < n; ++j) {
int cnt = 0;
for (int i = 0; i < m; ++i) {
cnt += grid[i][j];
}
ans += max(cnt, m - cnt) * (1 << (n - j - 1));
}
return ans;
}
};Java
import java.util.*;
class Solution {
public int matrixScore(int[][] grid) {
int m = grid.length, n = grid[0].length;
for (int i = 0; i < m; ++i) {
if (grid[i][0] == 0) {
for (int j = 0; j < n; ++j) {
grid[i][j] ^= 1;
}
}
}
int ans = 0;
for (int j = 0; j < n; ++j) {
int cnt = 0;
for (int i = 0; i < m; ++i) {
cnt += grid[i][j];
}
ans += Math.max(cnt, m - cnt) * (1 << (n - j - 1));
}
return ans;
}
}复杂度
时间
O(m·n)
逐行翻一遍、逐列数一遍,每个格子只被碰常数次
空间
O(1)
原地翻转,只用计数变量,不开额外矩阵
看不够?换成动画再走一遍
上面的推演每一步都对应一帧动画。点开交互动画版,能一步步看着 翻转矩阵后的得分 的数据怎么变、指针怎么走,还能切 Python / Java / C++ 跟着练。
面试官可能追问
为什么「先翻行保首列、再翻列让 1 尽量多」这套贪心能保证全局最优?+
关键是两步互不干扰、又各自局部最优。第一步把每行首位都弄成 1,这一步谁也动不了,因为最高位的权重比后面所有位之和都大,任何为低位让步去牺牲最高位的方案一定更差。第一步定下来之后,首列以后绝不再翻,于是每一列就彼此独立了:翻某一列不会影响别的列。既然各列独立,每一列单独取「1 最多」的方向就是该列的最优,合起来自然是全局最优。所以两步贪心叠加,得到的就是最大得分。
处理列时为什么可以不真的翻矩阵,直接用计数公式算?+
因为一列翻或不翻,它能提供的 1 的个数只有两种结果:不翻是 cnt 个 1,翻了就是 m 减 cnt 个 1。我们要的是较多的那个,也就是 cnt 和 m 减 cnt 的较大值。每个 1 在这一列上的价值都是该位的权重 2 的 (n 减 j 减 1) 次方,所以这一列对答案的贡献直接就是较大值乘权重。这样连矩阵都不用真改,一遍累加就出结果,省了真翻列的开销。
想听吴师兄把这道题讲给你听?
文字版和动画都随便看。开通图解算法年卡,可以听吴师兄声线把 翻转矩阵后的得分 一步步讲透(全站 0 节语音精讲,持续扩充), 卡住的地方还有 AI 私教小欧就着动画帮你拆到懂。
本平台为独立第三方培训机构,与华为技术有限公司无任何关联;课程的服务内容与权益以购买协议为准,学习效果因个人情况而异。「华为 OD」「华为可信」等仅为对岗位与考试方向的客观描述,相关商标归各自权利人所有。