按列翻转得到最大值等行数 图解题解
这道题到底在问什么
- 输入
- matrix=[[0,1],[1,1]]
- 输出
- 1
- 输入
- matrix=[[0,1],[1,0]]
- 输出
- 2 · 翻第 0 列后两行各自全相等
先想最直接的笨办法
整道题收束成一句话:把每行规范化成模式,模式相同的行能一起翻成行内全相等,哈希数出现次数最多的那种模式,次数就是答案。这里是 3。一次扫描就解决,不用真去枚举翻哪些列。(动画第 24 步)
最优解:为什么这么做
一句话答案:LeetCode 1072 按列翻转得到最多相等行:把每行以首元素为基准规范化成 0/1 模式,模式相同的行能一起翻成行内全相等,哈希数出现最多的那种模式即答案,时间 O(m·n)。
翻列翻的是什么,最后要数哪种行
给一个只含 0 和 1 的矩阵 matrix,一次操作是挑一整列、把这列里每个 0 和 1 全部取反,可以翻任意多列。要数的不是两行彼此相等,而是某一行自己内部的值全都一样(全 0 或全 1)。翻完之后最多能有多少行做到行内全相等?题面示例 matrix=[[0,1],[1,0]],翻第 0 列后两行各自全相等,答案是 2。
枚举所有翻法,2ⁿ 会把时间拖垮
n 列里每一列都有翻或不翻两个选择,把所有翻法枚举一遍就是 2ⁿ 种组合,每种还要扫一遍矩阵数出有几行达标,合计 O(2ⁿ·m·n)。列数刚到二十,2ⁿ 就过百万,这条硬枚举的路根本走不动,得找个只扫一遍矩阵就能定答案的办法。
翻掉一整列,行内的相对模式为什么纹丝不动
先盯住单独一行:要让它行内全相等,翻法其实是定死的——凡是和首元素不同的那些列都翻掉,这行立刻变成全 0 或全 1。所以每行都自带一张『翻法清单』。
这张清单只记录行内各格与首元素的相对关系,而翻一整列恰恰动不了这层关系:某格本来和首元素相同,翻这列时首元素也在这列、跟着一起翻,两者仍旧相同;本来不同的也依然不同。既然翻列不改变行内的相对模式,两行能不能用同一组翻列一起达标,就只看它们的相对模式是否完全一致。把每行按『和首元素相同记 0、不同记 1』压成一串 0/1 模式,模式相同的行就是能一起翻齐的行。
统一以首元素定 0 与 1,再拿哈希数模式
落到代码上只有两步。第一步规范化:所有行用同一个口径,以首元素为基准、同记 0 异记 1,这么一压,原始就相同的行和每一位都相反的互补行会得到同一串模式、落进同一个键。第二步计数:把每行的模式丢进哈希表,按键累加次数、平均常数时间就能存取,最后取出现次数最多的那种模式,它的次数就是能同时行内全相等的最大行数。口径必须全表统一,若一行拿首元素当基准、另一行拿别的列,同类就会被拆散、答案跟着错。
对着题面两个矩阵,亲手数一遍模式
第一个示例 matrix=[[0,1],[1,1]]:第一行 [0,1] 首元素是 0,保留原样,模式记成 01;第二行 [1,1] 首元素是 1,每一位异或 1、也就是两位相同得 0 不同得 1,翻过来模式记成 00。两串模式各出现 1 次,最多 1 行,答案 1。第二个示例 matrix=[[0,1],[1,0]]:第一行 [0,1] 模式还是 01;第二行 [1,0] 首元素是 1,异或 1 之后也得到 01。同一串 01 出现了 2 次,答案是 2——对应翻第 0 列,两行正好各自全相等。
互补行凭什么同组,模式全不同又剩几行
复杂度上,每格只参与一次模式计算,时间 O(m·n);哈希表最坏要存 m 个长度为 n 的键,空间 O(m·n)。有两处地方容易想岔。一处是互补的两行,像 [0,1] 和 [1,0],表面每位都相反,规范化后却落到同一个键、能一起达标数成 2,别被相反的外表骗过去。另一处是每行模式各不相同时,哈希表里每个键都只有 1,最多只能有一行做到行内全相等。最后别把题意读成『找两行相等』,它要的是一行内部全相同,目标看错,后面全白算。
▶ 动画逐步走查(共 22 步)——想跟着动画一帧帧对照就展开
- 3记牢这把尺子:每行压成一串 0/1 模式,首位永远是 0,模式一样的行能一起翻成行内全相等。下面每一帧都在套它。
- 4原矩阵 4 行 4 列,每格非 0 即 1这是原始矩阵,绿格是 1、蓝格是 0。我们一行一行处理,给每行算一个模式串,再把模式丢进右边的计数表。先想清楚一件事:每行单独都能翻成行内全相等(把和首格不同的列翻掉就行),所以要数的不是一行行不行,关键是哪些行需要翻的列一模一样、能一起翻齐。模式串记的,就是每行该翻哪些列。
- 5以每行首元素为基准,其余格和它比「同记 0、异记 1」高亮的这一列是每行的「基准」,也就是各行的第 0 个元素。规范化的做法是:让基准位永远记成 0,其余每格和基准比,一样记 0、不一样记 1。这串 0/1 其实是一张「翻法清单」:记 1 的列要翻、记 0 的列不翻,翻完整行就都和首格对齐、变成全相等。两行如果清单(模式)一模一样,要翻的列就完全相同,自然能用同一组翻列一起变齐,所以模式才是判断的依据。
- 6第 0 行 = [0, 1, 1, 0],基准(首元素)= 0轮到第 0 行(紫色),内容是 [0, 1, 1, 0]。它的基准是首元素 0。接下来每个格子和这个基准比一比,一样写 0、不一样写 1,拼出这行的模式。
- 7第 0 格是 0,基准是 0,相同记 0第 0 格就是基准本身,和自己当然相同,记成 0。模式开头先有一个 0,后面所有格都拿它当尺子。
- 8第 1 格是 1,基准是 0,不同记 1第 1 格是 1,基准是 0,两者不同,记 1。模式拼到 01。
- 9第 2 格是 1,基准是 0,不同记 1第 2 格是 1,基准是 0,两者不同,记 1。模式拼到 011。
- 10第 3 格是 0,基准是 0,相同记 0第 3 格是 0,基准是 0,两者相同,记 0。模式拼到 0110。
- 11模式 0110 计数变成 1,当前最多 1第 0 行的模式是 0110。把它丢进右边计数表,出现 1 次。当前能凑齐行内全相等的行数最多是 1。
- 12第 1 行 = [1, 0, 0, 1],基准(首元素)= 1轮到第 1 行(紫色),内容是 [1, 0, 0, 1]。它的基准是首元素 1。接下来每个格子和这个基准比一比,一样写 0、不一样写 1,拼出这行的模式。
- 13逐格和基准 1 比,得到模式 0110第 1 行每个格子和基准 1 比:相同记 0、不同记 1,拼出来是 0110。注意,这和前面第 0 行的模式一模一样。
- 14模式 0110 计数变成 2,当前最多 2模式 0110 入表,现在出现 2 次。计数表里最高的一格是 2,它就是目前能凑齐行内全相等的最大行数。
- 15第 2 行 = [0, 0, 1, 0],基准(首元素)= 0轮到第 2 行(紫色),内容是 [0, 0, 1, 0]。它的基准是首元素 0。接下来每个格子和这个基准比一比,一样写 0、不一样写 1,拼出这行的模式。
- 16逐格和基准 0 比,得到模式 0010第 2 行每个格子和基准 0 比:相同记 0、不同记 1,拼出来是 0010。这是一个新模式。
- 17模式 0010 计数变成 1,当前最多 2模式 0010 入表,现在出现 1 次。计数表里最高的一格是 2,它就是目前能凑齐行内全相等的最大行数。
- 18第 3 行 = [1, 0, 0, 1],基准(首元素)= 1轮到第 3 行(紫色),内容是 [1, 0, 0, 1]。它的基准是首元素 1。接下来每个格子和这个基准比一比,一样写 0、不一样写 1,拼出这行的模式。
- 19逐格和基准 1 比,得到模式 0110第 3 行每个格子和基准 1 比:相同记 0、不同记 1,拼出来是 0110。注意,这和前面第 0 行的模式一模一样。
- 20模式 0110 计数变成 3,当前最多 3模式 0110 入表,现在出现 3 次。计数表里最高的一格是 3,它就是目前能凑齐行内全相等的最大行数。
- 210110 出现 3 次(第 0/1/3 行),0010 出现 1 次四行都规范化完了。计数表里 0110 出现 3 次、0010 出现 1 次。次数最多的是 0110,有 3 行。也就是说,有一组翻列能让这 3 行同时变成行内全相等。答案就是这个最大次数 3。
- 22对模式 0110,翻基准为 0 时记 1 的那两列即可验证一下 0110 这组。模式里记 1 的位是第 1 列和第 2 列(闪红的两列),把它们整列翻过来,就能让所有 0110 的行各自变成行内全相等。下一帧看翻完的样子。
- 23翻列后第 0 行全 0、第 1/3 行全 1,共 3 行达标翻完第 1、2 列:第 0 行变成 0000、第 1 行和第 3 行都变成 1111,这三行各自行内全相等(紫色高亮)。第 2 行变成 0100,还是混的、不达标。达标的恰好 3 行,和模式 0110 的出现次数对上了。
- 24出现最多的模式次数 = 行内全相等的最大行数 = 3整道题收束成一句话:把每行规范化成模式,模式相同的行能一起翻成行内全相等,哈希数出现次数最多的那种模式,次数就是答案。这里是 3。一次扫描就解决,不用真去枚举翻哪些列。
⚠️ 容易写错的地方
✗ 错:以为是要让「两行互相相等」
✓ 对:是让一行自己内部全相等(全 0 或全 1)
题目要的是行内所有值相同,不是行与行相等;翻一列会同时影响这一列的所有行,理解错目标整道题就跑偏
✗ 错:直接拿原始行当哈希键去数
✓ 对:先规范化成相对首元素的模式再当键
原始相同的行和互补的行(每位都相反)其实能用同一组翻列一起达标,只有规范化后它们才会落到同一个键上,被数到一起
✗ 错:各行规范化口径不一致
✓ 对:所有行统一以首元素为基准、同记 0 异记 1
本课固定用首元素当基准、同 0 异 1。关键是所有行的基准列和编码方向必须一致——别一行用首元素、一行用别的列,也别一行同 0 异 1、另一行同 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 maxEqualRowsAfterFlips(self, matrix: List[List[int]]) -> int:
cnt = Counter()
for row in matrix:
t = tuple(row) if row[0] == 0 else tuple(x ^ 1 for x in row)
cnt[t] += 1
return max(cnt.values())C++
#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 maxEqualRowsAfterFlips(vector<vector<int>>& matrix) {
unordered_map<string, int> cnt;
int ans = 0;
for (auto& row : matrix) {
string s;
for (int x : row) {
s.push_back('0' + (row[0] == 0 ? x : x ^ 1));
}
ans = max(ans, ++cnt[s]);
}
return ans;
}
};Java
import java.util.*;
class Solution {
public int maxEqualRowsAfterFlips(int[][] matrix) {
Map<String, Integer> cnt = new HashMap<>();
int ans = 0, n = matrix[0].length;
for (int[] row : matrix) {
char[] cs = new char[n];
for (int i = 0; i < n; ++i) {
cs[i] = (char) (row[0] ^ row[i]);
}
ans = Math.max(ans, cnt.merge(String.valueOf(cs), 1, Integer::sum));
}
return ans;
}
}复杂度
时间
O(m·n)
每行扫一遍算模式、各格只碰一次;哈希插入与查询平均 O(1)
空间
O(m·n)
哈希表最多存 m 个模式键,每个键长 n;最坏全不同就是 m·n
看不够?换成动画再走一遍
上面的推演每一步都对应一帧动画。点开交互动画版,能一步步看着 按列翻转得到最大值等行数 的数据怎么变、指针怎么走,还能切 Python / Java / C++ 跟着练。
面试官可能追问
为什么只要两行『模式相同』,就一定能用同一组翻列让它们各自行内全相等?+
每行的模式其实就是它专属的翻法清单:第 j 位记 1 表示这列要翻、记 0 表示不翻,照着自己的清单翻,任何一行都会被拉成全 0 或全 1。两行模式一模一样,说明它们要翻的列集合完全相同,于是同一组翻列同时作用在两行上,会把两行都拉成行内全相等。模式一旦不同,要翻的列就对不齐,满足了这行就破坏那行,怎么翻都没法让它们同时全相等。所以『模式相同』既是能一起翻齐的充分条件,也是必要条件。
为什么要先规范化,直接拿原始行当哈希键会漏掉什么?+
直接拿原始行当键,只会把长得一模一样的行数到一起,可原始相反的两行(每一位都取反的互补行)其实也能用同一组翻列一起达标,本该算作一类。规范化就是把每行统一改写成『相对首元素』的模式,原始相同的行和互补的行因此得到同一串键、被数进同一格。少了这步,互补的行各算各的,答案会偏小。
除了哈希,还能用别的办法数出最多的那种模式吗?+
能,只是都不如哈希顺手。可以把每行模式转成字符串后整体排序,相同模式会排在一起,再数最长的一段连续相同,代价是排序多一个 log 因子。若列数不多,也能把每串 0/1 模式当二进制压成一个整数、拿它当下标去数。面试时先给哈希这版 O(m·n) 的解法,再补一句排序或位压缩的权衡,思路就完整了。
想听吴师兄把这道题讲给你听?
文字版和动画都随便看。开通图解算法年卡,可以听吴师兄把 按列翻转得到最大值等行数 一步步讲透(全站已上线 905 份讲透,持续扩充), 卡住的地方还有 AI 私教小欧就着动画帮你拆到懂。
本平台为独立第三方培训机构,与华为技术有限公司无任何关联;课程的服务内容与权益以购买协议为准,学习效果因个人情况而异。「华为 OD」「华为可信」等仅为对岗位与考试方向的客观描述,相关商标归各自权利人所有。