题目描述
字符串 S 由小写字母组成。我们要把这个字符串划分为尽可能多的片段,同一字母最多出现在一个片段中。返回一个表示每个字符串片段的长度的列表。
示例:
输入:S = “ababcbacadefegdehijhklij”
输出:[9,7,8]
解释: 划分结果为 “ababcbaca”, “defegde”, “hijhklij”。每个字母最多出现在一个片段中。像 “ababcbacadefegde”, “hijhklij” 的划分是错误的,因为划分的片段数较少。
提示:
- S的长度在[1, 500]之间。
- S只包含小写字母 ‘a’ 到 ‘z’ 。
解题思路
贪心解法
为了满足贪心策略,我们通常需要一些预处理,其他几遍贪心策略解决的算法的文章出现排序,这属于一种预处理。但是还有很多其他的预处理,例如在处理数组前,统计一遍信息(如频率、个数、第一次出现的位置、最后一次出现的位置等),可以让题目变得简单明了。
这道题我们先遍历一遍字符串得到每个字母的最后一次出现的位置,然后使用贪心策略将字符串划分为尽可能多的片段,具体做法如下:
- 从左到右遍历字符串,记录当前维护片段的开始下表start和end,初始化时start=end=0;
- 对于每个出现的字母c,将预处理得到的这个字母的最后出现的下标和当前维护片段end比较,可以知道当前片段的end一定不会小于这个字母的最后出现的下标,所以如果当前出现字母的最后出现的下标大于当前维护片段的end,更新end为end = lastPos[v-‘a’];
- 当访问到end时,当前偏大u那你呢访问结束,片段的长度为$end-start+1$,将该长度添加到结果,更新$start=end+1$继续寻找下一个片段;
- 重复上述1-3步直至遍历完字符串。
上述做法贪心的思想体现在寻找每个片段可能的最小结束下标,可以保证每个片段的长度一定是满足要求的最短长度,如果取更短长度,就会出现同个字母出现在多个片段中。由于每个片段的结束标志是访问到下表end,因此可以保证每个片段的每个字母一定在当前片段内,不可能出现在其他片段,保证同一个出现在一个片段内的条件成立。
代码
golang实现:
func partitionLabels(s string) (ans []int) {
lastPos := [26]int{}
for i, v := range s {
lastPos[v-'a'] = i
}
var start, end int
for i, v := range s {
if lastPos[v-'a'] > end {
end = lastPos[v-'a']
}
if i == end {
ans = append(ans, end-start+1)
start = end + 1
}
}
return
}
时间和空间复杂度
- 时间复杂度:$O(n)$,其中 $n$ 是字符串的长度。需要遍历字符串两次,第一次遍历时记录每个字母最后一次出现的下标位置,第二次遍历时进行字符串的划分。
- 空间复杂度:$O(∣Σ∣)$,其中 $Σ$ 是字符串中的字符集。这道题中,字符串只包含小写字母,因此$∣Σ∣=26$。