LeetCode 763. 划分字母区间
题目描述
题意分析
要把字符串切成若干连续且不重叠的片段,切完后拼起来仍是原串,同时满足「同一个字母不能跨片段出现」,在所有满足条件的切法里选片段数最多的那一种,返回各片段的长度。
约束给的信号很干净:字符串只含 26 个小写字母,长度 500 量级。字符集固定意味着「每个字母的信息」可以用一个长度 26 的数组存下,$O(1)$ 空间;长度不大则说明 $O(n^2)$ 也能过,但显然存在更直接的线性做法。
边界要想到三种:整串可能一段都切不开,比如"abac"只能整体作为一段;每个字母都只出现一次时可以切成 $n$ 段;答案里所有长度之和必然等于串长,这是一个很好用的自检条件。
解法:记录最后位置后贪心切分
核心思路
每个字母只能出现在一个片段中。只要当前片段包含字母
c,片段右端就至少要覆盖c的最后出现位置;至于中间出现了几次并不重要。因此先预处理每个字母的最后下标,再从左到右确定片段边界。扫描当前片段时维护
end,它表示已读字符要求覆盖到的最远位置。处理到i后始终有:
end = max(last[s[k]]),start <= k <= i这条不变量说明:
i < end时不能切,因为当前片段中至少有一个字母还会在后面出现;i == end时可以切,因为片段内所有字母都已在此处或此前最后一次出现。为什么此时立刻切最优?
i == end是当前片段最早的合法右端点。把切点继续向右推只会合并本可独立的后缀,不可能增加片段数,所以选择最早合法切点能得到最多片段。
解题步骤
- 扫描字符串,用长度为 26 的数组
last记录每个字母最后一次出现的下标。- 用
start表示当前片段起点,用end表示当前片段必须覆盖的最远位置。- 再次从左到右扫描,令
end = max(end, last[s[i]])。- 当
i == end时,记录当前片段长度end - start + 1,再令start = i + 1。- 扫描结束后,所有片段长度之和应等于原字符串长度。
以
"ababcbacadefegdehijhklij"为例:首字符a把第一段右端定到8,中途遇到的b、c都没有把它推得更远,于是在8切出长度9;第二段的边界由d的14扩到e的15,切出长度7;第三段最终被j扩到23,切出长度8,答案为[9, 7, 8]。
代码实现
class Solution {
public List<Integer> partitionLabels(String s) {
int[] last = new int[26];
for (int i = 0; i < s.length(); i++) {
last[s.charAt(i) - 'a'] = i;
}
List<Integer> ans = new ArrayList<>();
int start = 0;
int end = 0;
for (int i = 0; i < s.length(); i++) {
end = Math.max(end, last[s.charAt(i) - 'a']);
if (i == end) {
ans.add(end - start + 1);
start = i + 1;
}
}
return ans;
}
}
func partitionLabels(s string) []int {
last := [26]int{}
for i := 0; i < len(s); i++ {
last[s[i]-'a'] = i
}
ans := make([]int, 0)
start, end := 0, 0
for i := 0; i < len(s); i++ {
if last[s[i]-'a'] > end {
end = last[s[i]-'a']
}
if i == end {
ans = append(ans, end-start+1)
start = i + 1
}
}
return ans
}
复杂度分析
- 时间复杂度:
O(n)。一次统计最后位置,一次扫描并切分。- 空间复杂度:
O(1)。字符集固定为 26 个小写字母;返回结果不计入额外空间。
关键点总结
- 将“同一字母不能跨片段”转化为“片段必须覆盖该字母的最后出现位置”。
end不是某一个字母的末位置,而是当前片段内所有字母末位置的最大值。i == end同时给出合法性与最优性:此处能切,并且是最早能切的位置。- 面试时应能明确说出不变量,并用“推迟切点不会增加段数”解释贪心正确性。
易错点总结
- 预处理必须记录最后出现位置,而不是第一次出现位置或出现次数。
- 更新右端点必须取
max;直接赋值会让边界回退。例如"abac"中看到b时不能把边界从2改回1。- 不能只判断当前字符是否最后一次出现,还要满足当前片段内所有字符的约束。例如
"abba"只能整体成段。- 片段长度是
end - start + 1,切分后要把start更新为i + 1。- 不要边统计最后位置边切分;未来位置尚未知时会过早切开
"abac"。
相似题目
| 题目 | 难度 | 考察点 |
|---|---|---|
| 45. 跳跃游戏 II | 中等 | 同样维护最远可达边界,但求到达终点的最少步数 |
| 55. 跳跃游戏 | 中等 | 单调扩张边界,判断能否覆盖到末尾 |
| 56. 合并区间 | 中等 | 区间由输入直接给出,需先排序再合并重叠 |
| 57. 插入区间 | 中等 | 在已有序区间中插入新区间并局部合并 |
| 228. 汇总区间 | 简单 | 按数值连续性切段,边界条件来自相邻差值 |
| 435. 无重叠区间 | 中等 | 按右端点排序贪心,求最少删除数 |
| 452. 用最少数量的箭引爆气球 | 中等 | 求最少的公共交点数,与本题的最多段数互为对偶 |