763-划分字母区间
返回给你一个字符串
s。我们要把这个字符串划分为尽可能多的片段,同一字母最多出现在一个片段中。注意,划分结果需要满足:将所有划分结果按顺序连接,得到的字符串仍然是
s。返回一个表示每个字符串片段的长度的列表。
示例
示例 1:
输入:s = "ababcbacadefegdehijhklij"
输出:[9,7,8]
解释:
划分结果为 "ababcbaca"、"defegde"、"hijhklij" 。
每个字母最多出现在一个片段中。
示例 2:
输入:s = "eccbbbbdec"
输出:[10]
思路:贪心 + 双指针
这道题的核心思想是:保证当前片段的末尾边界大于等于片段中所有字符最后出现的位置。
两步走:
-
预处理:记录每个字符最后出现的位置
- 遍历一遍字符串,用数组
last[26]记录每个字母最后一次出现的下标
- 遍历一遍字符串,用数组
-
遍历字符串,贪心划分
- 维护两个指针:
start(当前片段的起始位置)和end(当前片段的最远边界) - 遍历过程中,不断更新
end = max(end, last[当前字符]) - 当
i == end时,说明已经到达了当前片段的最远边界,可以划分了 - 记录片段长度
end - start + 1,然后更新start = end + 1
- 维护两个指针:
为什么这样是对的?
- 贪心的正确性在于:我们尽可能早地划分片段
- 只要当前片段包含了所有出现过的字符的最后位置,就可以安全划分
- 这样能保证片段数量最多,且同一字母只出现在一个片段中
代码实现
class Solution {
public List<Integer> partitionLabels(String s) {
// 核心就是保证当前片段的末尾边界大于等于片段中所有字符最后出现的位置
// 1. 记录所有字符出现的位置
int[] last = new int[26];
for (int i = 0; i < s.length(); i++) {
last[s.charAt(i) - 'a'] = i;
}
// 2. 遍历字符串,直到到达当前字符最远边界
int start = 0;
int end = 0;
ArrayList<Integer> list = new ArrayList<>();
for (int i = 0; i < s.length(); i++) {
// 不断更新最远边界
end = Math.max(end, last[s.charAt(i) - 'a']);
// 直到到达最远边界,进行划分
if (i == end) {
list.add(end - start + 1);
start = end + 1;
}
}
return list;
}
}
复杂度分析
- 时间复杂度: O(n),其中 n 是字符串的长度。需要遍历字符串两次,第一次记录每个字符最后出现的位置,第二次进行贪心划分。
- 空间复杂度: O(1),只需要一个大小为 26 的数组记录每个字母最后出现的位置(假设只包含小写字母)。
关键点总结
| 关键点 | 说明 |
|---|---|
| 预处理 | 先记录每个字符最后出现的位置,这是贪心的基础 |
| 双指针 | start 标记片段起点,end 标记片段最远边界 |
| 贪心策略 | 尽可能早地划分,只要当前片段包含了所有字符的最后位置 |
| 边界条件 | 当 i == end 时,说明可以划分了 |
类似题目
- 56. 合并区间 - 也是区间合并问题
- 45. 跳跃游戏 II - 贪心 + 边界更新