题目描述
思路解析
一句话答案:LeetCode 1007 行相等的最少多米诺旋转:要让一整行全相同,能成的目标值只可能是第一列的上下两个数,逐候选各扫一遍数翻转取最少、都不行返回 -1,时间 O(n)。
两行多米诺,要让某一行的点数全相同
给两个等长数组 tops 和 bottoms,tops[i] 是第 i 列上面的点数,bottoms[i] 是下面的点数。每次操作能把某一列翻过来(上下点数对调),问最少翻几列,能让上行或下行全相同;做不到返回 -1。题面给 tops=[2,1,2,4,2,2]、bottoms=[5,2,6,2,3,2],答案是 2;若像 tops=[3,5,1,2,3]、bottoms=[3,6,3,3,4] 没有数盖得住每一列,就返回 -1;而 tops=[1,1,1] 上行本就全是 1,一次不用翻,返回 0。
要统一成哪个数,难道 1 到 6 挨个试
点数就 1 到 6 六种,一个笨办法是拿每个数轮流当目标 x,扫一遍全列数需要翻几列,六个候选挑翻得最少的。这能算出答案,但多数候选一上手就没戏、白扫一趟,得先看清哪些 x 不可能成。
能成的目标值,只可能是第一列的两个数
盯住第 0 列:上是 2、下是 5。整行若要全变成 x,第 0 列也必须出得了 x——要么上面本就是 x,要么翻过来让下面的 x 顶上去。可这列只有 2 和 5,x 就只能是 2 或 5,别的值第 0 列出不来、整行凑不齐。六选一坍缩成二选一:把 x=tops[0] 和 x=bottoms[0] 各验一遍,谁翻得少取谁,都不行才返回 -1。
定住一个候选 x,上行下行各要翻几列
验候选 x 时,top、bottom 两个计数器都从 0 起,分别记「上行全变成 x 要翻几列」和「下行全变成 x 要翻几列」。逐列看,只要上或下有一个是 x 就有救——上面已是 x 则 top 不动、否则加 1,下面已是 x 则 bottom 不动、否则加 1。某列上下都不是 x,候选当场作废。走完全列,min(top, bottom) 就是 x 的代价。
x=2 和 x=5 各要数几张翻转
先试 x=2。第 0 列上 2 下 5,下行要翻,top=0、bottom=1;第 1 列上 1 下 2,上行要翻,top=1、bottom=1;第 2 列上 2 下 6,下行翻,top=1、bottom=2;第 3 列上 4 下 2,上行翻,top=2、bottom=2;第 4 列上 2 下 3,下行翻,top=2、bottom=3;第 5 列上下都是 2,两边不动,top=2、bottom=3,全列过关,min(2,3)=2。
再试 x=5。第 0 列上 2 下 5,上面要翻,top=1、bottom=0;到第 1 列上 1 下 2 都不是 5,这列出不来 5,候选 5 作废。两候选里 x=2 要 2 张、x=5 灭,答案就是 2。
把 -1 当成 0,或只算半边账
枚举 1 到 6 每个点数当候选纯属白费,第 0 列出不了的值整行都出不来,锁死成两个候选就够。同一个 x 若只数上行、忘了下行,可能上行翻 3 列而下行只翻 1 列,更省的那半就漏了。某列上下都不是 x 时把无解写成返回 0 也不对:0 是本来就相同、-1 是根本做不到,两者反着来。全程最多两个候选各扫一遍 n 列,共 2n 次,时间 O(n),空间 O(1)。
▶ 动画逐步走查(文字版)——想跟着上方动画一帧帧对照就展开
记住这套「候选只有第 0 列的两个数 → 对每个候选扫一遍数翻转」,下面每帧都在套它。
先看第 0 列:上是 2、下是 5。整行要统一成 x,第 0 列必须出得了 x,所以 x 只能是 2 或 5。我们先试 x = 2。
开扫候选 x=2:用 top 记「想统一上行需翻几张」,bottom 记「想统一下行需翻几张」,两个都从 0 开始。
看第 0 列:上 2、下 5。只要上或下里有一个是 2,这列就有救。
第 0 列:上是 2、下是 5:上行不用翻;若想让下行变 2,这列得翻一张(bottom+1)。 累计 top=0、bottom=1。
看第 1 列:上 1、下 2。只要上或下里有一个是 2,这列就有救。
第 1 列:上是 1(不是 2),但下是 2:若想让上行变 2,这列得翻一张(top+1);下行本就是 2,下行不用翻。 累计 top=1、bottom=1。
看第 2 列:上 2、下 6。只要上或下里有一个是 2,这列就有救。
第 2 列:上是 2、下是 6:上行不用翻;若想让下行变 2,这列得翻一张(bottom+1)。 累计 top=1、bottom=2。
看第 3 列:上 4、下 2。只要上或下里有一个是 2,这列就有救。
第 3 列:上是 4(不是 2),但下是 2:若想让上行变 2,这列得翻一张(top+1);下行本就是 2,下行不用翻。 累计 top=2、bottom=2。
看第 4 列:上 2、下 3。只要上或下里有一个是 2,这列就有救。
第 4 列:上是 2、下是 3:上行不用翻;若想让下行变 2,这列得翻一张(bottom+1)。 累计 top=2、bottom=3。
看第 5 列:上 2、下 2。只要上或下里有一个是 2,这列就有救。
第 5 列:上下都是 2,怎么放都行,两边都不用翻。 累计 top=2、bottom=3。
候选 x=2 全列都过关。凑上行要翻 2 张、凑下行要翻 3 张,取较小的 → 2 张。这就是 x=2 的代价。
别忘了还有第二个候选:把整行统一成下行第 0 个的值 x=5。计数器清零,从第 0 列重新扫。
候选 5:看第 0 列,上 2、下 5。这一列里有没有 5?
第 0 列有 5,本列过关。累计 top=1、bottom=0。
候选 5:看第 1 列,上 1、下 2。这一列里有没有 5?
第 1 列上 1、下 2,两个都不是 5:无论翻不翻,这列都出不来 5。候选 5 当场出局(红色那列卡死)。
两个候选里:x=2 要 2 张,x=5 出局。取能成的最小值 → 答案 2。若两个候选都出局,则返回 -1。
边界先想清:单列与已相同都是 0;没有候选能盖住全列才是 -1。
两个高频追问:必须试两个候选;极大值是为了统一参与 min。
参考代码
from typing import Listclass Solution: def minDominoRotations(self, tops: List[int], bottoms: List[int]) -> int: def check(x): top = bottom = 0 for a, b in zip(tops, bottoms): if a != x and b != x: return 10**9 if a != x: top += 1 if b != x: bottom += 1 return min(top, bottom) ans = min(check(tops[0]), check(bottoms[0])) return -1 if ans == 10**9 else ans复杂度
- 时间:O(n),最多两个候选,每个扫一遍 n 列,共 2n
- 空间:O(1),只用 top、bottom 几个计数器
易错点
面试追问把动画讲成自己的话
追问如果只试 tops[0] 一个候选,会漏答案吗?
追问为什么用「极大值」当无解标记,而不是直接 return -1?
这道题到这就讲完了。动画和文字是同一套思路——别光看,关掉页面自己默写一遍。然后顺着主线继续:
两地调度
LeetCode 1029 · 中等 · 沿着 贪心套路 继续往下推进
把这道题真正学会,再走
图解算法年卡 ¥99 /年
- ✓本题每步动画的吴师兄语音讲解(全站陆续覆盖)
- ✓小欧带 8 步通关训练——追问到不看答案也能写对、能 30 秒讲给面试官听
- ✓学习报告,记录每道题的掌握程度
76k+ GitHub Star · 吴师兄开源图解算法,几十万开发者在看的算法讲解
想成体系刷透这类套路?去图解算法专题