使绳子变成彩色的最短时间 图解题解
这道题到底在问什么
- 输入
- colors="abaac", time=[1,2,3,4,5]
- 输出
- 3 (连续 aa 里移掉较小的 3)
- 输入
- colors="abc", time=[1,2,3]
- 输出
- 0 (本来就相邻不同,无需移除)
最优解:为什么这么做
一句话答案:LeetCode 1578 让字符串多彩的最少时间:相邻同色气球只能留一个,把连续同色切成一段段,每段留删除代价最大的、其余代价累加,段总和减段最大就是这段开销,时间 O(n)、空间 O(1)。
让相邻气球颜色不同,最少花多少删除时间
一排气球串成绳子,colors 记每个气球的颜色,neededTime[i] 是删掉第 i 个气球要花的时间。允许删掉任意几个,只要剩下的相邻颜色都不同就行,问最少花多少时间。题面例子 colors="abaac"、neededTime=[1,2,3,4,5],答案是 3。碍事的只有连续同色的气球,本来交替的不用动。
同色连成一串,到底删到只剩谁
一段连续同色里相邻颜色全相同、都算违规,这段最后只能留一个、其余全删。为难的是留哪个:留下的不花钱,被删的每个都按 neededTime 付时间。要挨个试「保留第几个、删其余」再比总花费,一段长 k 就得试 k 种,几段叠起来白算几遍。
为什么每段都留最贵的那个最省
既然一段里非留一个不可,那留谁、删谁只看怎么最省。被删的时间要全付,留下的不付,想让总付出最小,就把时间最大的那个留下当免死金牌、其余删掉。于是这段开销正好等于段内所有时间之和,减去段内最大的那个。
段与段之间:这段留谁管不着隔壁那段,两段的账各算各的。每段各取本段最省,拼起来就是整条绳子最省,这就是贪心在这题里的样子:按连续同色段切开、逐段独立结算。
一趟扫过去,边走边把每段结清
不必真把每段切出来存着。备三个累加器:ans 记已结清总开销,group_sum 记当前段时间之和,group_max 记当前段最大时间。从左往右扫每个气球跟前一个比颜色:变了就先结清上一段(ans 加 group_sum 减 group_max),再把两者清零开新段;同色就把当前时间累进 group_sum,并更新 group_max。
收尾最容易漏:循环走完,最后一段身后没有下一个不同颜色触发结算,得在返回前再补结一次。整条绳子只扫一遍,时间 O(n);全程只用三个变量,空间 O(1)。
abaac、[1,2,3,4,5] 亲手结一遍
开头 a(1) 开一段,group_sum=1、group_max=1。第二个 b(2) 变色,先结上一段:加 1 减 1 得 0,ans 还是 0;新段 group_sum=2、group_max=2。第三个 a(3) 又变色,结算加 2 减 2 得 0,ans 仍是 0;新段 group_sum=3、group_max=3。
第四个还是 a(4),同色累加:group_sum=3+4=7,group_max=max(3,4)=4。第五个 c(5) 变色,把这段 aa 结掉:加 7 减 4 得 3,ans=3;新段 group_sum=5、group_max=5。扫到末尾补结最后一段 c:加 5 减 5 得 0。ans 停在 3,只需删掉那段 aa 里较便宜的 a(3),正好花 3。
常把开销算歪的几个错法
最常撞的是循环结束忘了补结最后一段:末段没人触发结算,开销就从答案里蒸发,样例末段 c 恰好是 0 不显眼,换组数就少算一大截。另一个是把某段全删光:一段只需留一个就满足相邻不同,留最贵的省最多,全删等于白扔一份最大时间。还有人按「段长减一个最小值」凑,可代价算的是时间不是个数,写出来必须是段总和减段最大。
▶ 动画逐步走查(共 22 步)——想跟着动画一帧帧对照就展开
- 3记住这句:每段留最贵的、移其余,代价 = 段总和 减 段最大。下面每帧都在套它。
- 4开局:第 0 个气球 红(3) 单独开一段(绿色),组内 sum=3、max=3,还没算移除代价。
- 5看第 1 个 红(5),和前一个 红 比颜色。绿色是当前正在生长的同色段。
- 6颜色相同,第 1 个 红(5) 并进当前段。组内 sum 累到 8,max 取到 5(先不结算,等这段结束再算)。
- 7看第 2 个 蓝(10),和前一个 红 比颜色。绿色是当前正在生长的同色段。
- 8颜色变了,上一段结算:段内留下最贵的 5(红框=保留),移掉其余,本段代价 8 减 5 = 3。新一段从第 2 个 蓝(10) 开始(绿色)。
- 9看第 3 个 蓝(7),和前一个 蓝 比颜色。绿色是当前正在生长的同色段。
- 10颜色相同,第 3 个 蓝(7) 并进当前段。组内 sum 累到 17,max 取到 10(先不结算,等这段结束再算)。
- 11看第 4 个 蓝(5),和前一个 蓝 比颜色。绿色是当前正在生长的同色段。
- 12颜色相同,第 4 个 蓝(5) 并进当前段。组内 sum 累到 22,max 取到 10(先不结算,等这段结束再算)。
- 13看第 5 个 红(3),和前一个 蓝 比颜色。绿色是当前正在生长的同色段。
- 14颜色变了,上一段结算:段内留下最贵的 10(红框=保留),移掉其余,本段代价 22 减 10 = 12。新一段从第 5 个 红(3) 开始(绿色)。
- 15看第 6 个 蓝(5),和前一个 红 比颜色。绿色是当前正在生长的同色段。
- 16颜色变了,上一段结算:段内留下最贵的 3(红框=保留),移掉其余,本段代价 3 减 3 = 0。新一段从第 6 个 蓝(5) 开始(绿色)。
- 17看第 7 个 蓝(5),和前一个 蓝 比颜色。绿色是当前正在生长的同色段。
- 18颜色相同,第 7 个 蓝(5) 并进当前段。组内 sum 累到 10,max 取到 5(先不结算,等这段结束再算)。
- 19看第 8 个 蓝(4),和前一个 蓝 比颜色。绿色是当前正在生长的同色段。
- 20颜色相同,第 8 个 蓝(4) 并进当前段。组内 sum 累到 14,max 取到 5(先不结算,等这段结束再算)。
- 21看第 9 个 蓝(8),和前一个 蓝 比颜色。绿色是当前正在生长的同色段。
- 22颜色相同,第 9 个 蓝(8) 并进当前段。组内 sum 累到 22,max 取到 8(先不结算,等这段结束再算)。
- 23扫到末尾,把最后一段也结算:留下最贵的 8(红框),代价 22 减 8 = 14。
- 24全程结束:每段绿色那个是保留的(共 4 段、4 个气球留下),灰色都被移除。各段代价相加 = 29,这就是答案。
⚠️ 容易写错的地方
✗ 错:每段把所有气球都移掉
✓ 对:每段留下最贵的那个、移其余
段内只需让相邻不同,留 1 个即可,留最贵的最省
✗ 错:循环结束忘了结算最后一段
✓ 对:出循环后再补一次 ans += sum 减 max
最后一段没有「下一个不同颜色」来触发结算,会漏掉
✗ 错:用「段长 减 1 个最小」去算
✓ 对:应是「段总和 减 段最大」
代价是时间不是个数,移掉的恰好是除最大外的全部,和等于总和减最大
完整代码(Python / C++ / Java)
Python
from typing import List
class Solution:
def minCost(self, colors: str, neededTime: List[int]) -> int:
ans = group_sum = group_max = 0
prev = ''
for c, t in zip(colors, neededTime):
if c != prev:
ans += group_sum - group_max
group_sum = group_max = 0
prev = c
group_sum += t
group_max = max(group_max, t)
return ans + group_sum - group_maxC++
#include <algorithm>
#include <string>
#include <vector>
using namespace std;
class Solution {
public:
int minCost(string colors, vector<int>& neededTime) {
int ans = 0, sum = 0, mx = 0;
char prev = 0;
for (int i = 0; i < (int)colors.size(); ++i) {
if (colors[i] != prev) {
ans += sum - mx;
sum = mx = 0;
prev = colors[i];
}
sum += neededTime[i];
mx = max(mx, neededTime[i]);
}
return ans + sum - mx;
}
};Java
import java.util.*;
class Solution {
public int minCost(String colors, int[] neededTime) {
int ans = 0, sum = 0, max = 0;
char prev = 0;
for (int i = 0; i < colors.length(); i++) {
if (colors.charAt(i) != prev) {
ans += sum - max;
sum = max = 0;
prev = colors.charAt(i);
}
sum += neededTime[i];
max = Math.max(max, neededTime[i]);
}
return ans + sum - max;
}
}复杂度
时间
O(n)
从头到尾扫一遍气球
空间
O(1)
只用 sum、max、ans 三个变量
看不够?换成动画再走一遍
上面的推演每一步都对应一帧动画。点开交互动画版,能一步步看着 使绳子变成彩色的最短时间 的数据怎么变、指针怎么走,还能切 Python / Java / C++ 跟着练。
面试官可能追问
要是改成整条绳子颜色全不同,而不只是相邻不同,怎么办?+
那就不再是分段贪心了。相邻不同只需盯住连续同色段,全不同则要求每种颜色最多留一个,得按颜色本身聚合:把同一种颜色的所有气球归到一起,留下时间最大的、删掉其余,再把各颜色的删除开销加起来。思路仍是「留最贵、删其余」,只是分组的钥匙从「连续段」换成了「颜色」。本题只要求相邻不同,所以只按连续段处理。
为什么不显式把每段切出来,也能算对?+
因为每段的账只依赖这一段的 sum 和 max,跟别段无关,边扫边结算就够了。维护当前段的 group_sum 和 group_max,遇到和前一个不同的颜色,就先把上一段结清(ans 加 group_sum 减 group_max)再清零开新段,同色就往里累加。全程不用把段落存进任何数组,三个变量走一遍即可,这也是三语言参考代码用的写法。
空间为什么能压到 O(1)?+
整个过程只需要记住三样东西:已结清的总开销 ans、当前段的时间和 group_sum、当前段的最大时间 group_max。段一结算就立刻并进 ans、把两个组内变量清零复用,不保留任何一段的历史,也不额外开与气球数量成正比的数组,所以无论绳子多长,占用的额外空间都是固定的三个变量。
想听吴师兄把这道题讲给你听?
文字版和动画都随便看。开通图解算法年卡,可以听吴师兄把 使绳子变成彩色的最短时间 一步步讲透(全站已上线 905 份讲透,持续扩充), 卡住的地方还有 AI 私教小欧就着动画帮你拆到懂。
本平台为独立第三方培训机构,与华为技术有限公司无任何关联;课程的服务内容与权益以购买协议为准,学习效果因个人情况而异。「华为 OD」「华为可信」等仅为对岗位与考试方向的客观描述,相关商标归各自权利人所有。