题目:
给你一个字符串 s 。我们要把这个字符串划分为尽可能多的片段,同一字母最多出现在一个片段中。
注意,划分结果需要满足:将所有划分结果按顺序连接,得到的字符串仍然是 s 。
返回一个表示每个字符串片段的长度的列表。
示例 1:
输入:s = “ababcbacadefegdehijhklij”
输出:[9,7,8]
解释:
划分结果为 “ababcbaca”、“defegde”、“hijhklij” 。
每个字母最多出现在一个片段中。
像 “ababcbacadefegde”, “hijhklij” 这样的划分是错误的,因为划分的片段数较少。
示例 2:
输入:s = “eccbbbbdec”
输出:[10]
提示:
1 <= s.length <= 500
s 仅由小写英文字母组成
思路:
题目要求同一字母最多出现在一个片段中,那么如何把同一个字母的都圈在同一个区间里呢?
如果没有接触过这种题目的话,还挺有难度的。
在遍历的过程中相当于是要找每一个字母的边界,如果找到之前遍历过的所有字母的最远边界,说明这个边界就是分割点了。此时前面出现过所有字母,最远也就到这个边界了。
可以分为如下两步:
- 统计每一个字符最后出现的位置
- 从头遍历字符,并更新字符的最远出现下标,如果找到字符最远出现位置下标和当前下标相等了,则找到了分割点
以S = "ababcbacadefe"为例,进行详细分析如何切割。
首先进行遍历,遍历到第一个字符串为a, a的最后出现位置的下标为8,因为题目要求同一字母最多出现在一个片段,所以为了第一个片段包含所有的a,继续向下进行遍历,遍历到b,b的最后出现的位置的下标为5,继续遍历 此时需要包含a的同时包含所有的b(因为b的最远下标小于a,所以只要包含了所有的a则必定包含所有的b),继续向下遍历,又新加入了a和b,之后又加入了c,同理,接下来的遍历需要包含a,b的同时包含c。直到包含了所有遍历时加入的元素时,开始进行切割,这就是所求的一个片段的长度。下一个片段切割同理。
理解了切割过程,代码依然不好写,如何得知字符最后出现的位置?得知了位置又人如何切割?
这里需要用到哈希来获得字符最后出现的位置,通过字符串的ASCLL,减去a的ASCLL,每次遍历来更新字符串最后出现的位置。
代码如下:
# 初始化一个长度为26的数组,用于存储每个字母最后出现的位置path = [0] * 26for i in range(len(s)):# 更新字母最后出现的位置path[ord(s[i]) - ord('a')] = i
ord()函数的作用是获取字母的ASCLL值
接下来进行切割
每次遍历更新当前区间的右边界,直到右边界等于当前节点的值,则找到了切割点。
代码如下:
result = []left = 0right = 0for i in range(len(s)):# 更新当前区间的右边界right = max(path[ord(s[i]) - ord('a')], right)# 如果右边界等于当前位置,说明找到了一个划分点,将当前区间长度加入结果中,并更新左边界if right == i:result.append(right - left + 1)left = i + 1
本题还有一个思路,跟之前的452. 用最少数量的箭引爆气球和435. 无重叠区间一样的思路,不过写起来很麻烦这里就不进行详细说明,代码和注释在下面完整代码中给出,思路见:452. 用最少数量的箭引爆气球(贪心)
完整代码:
class Solution:def partitionLabels(self, s: str) -> List[int]:# 初始化一个长度为26的数组,用于存储每个字母最后出现的位置path = [0] * 26for i in range(len(s)):# 更新字母最后出现的位置path[ord(s[i]) - ord('a')] = iresult = []left = 0right = 0for i in range(len(s)):# 更新当前区间的右边界right = max(path[ord(s[i]) - ord('a')], right)# 如果右边界等于当前位置,说明找到了一个划分点,将当前区间长度加入结果中,并更新左边界if right == i:result.append(right - left + 1)left = i + 1return result
贪心(版本二)与452.用最少数量的箭引爆气球 、 435.无重叠区间相同的思路。
class Solution:def countLabels(self, s):# 初始化一个长度为26的区间列表,初始值为负无穷hash = [[float('-inf'), float('-inf')] for _ in range(26)]hash_filter = []# 遍历字符串s,更新每个字母的区间范围for i in range(len(s)):if hash[ord(s[i]) - ord('a')][0] == float('-inf'):hash[ord(s[i]) - ord('a')][0] = ihash[ord(s[i]) - ord('a')][1] = i# 将没有出现过的字母的区间过滤掉for i in range(len(hash)):if hash[i][0] != float('-inf'):hash_filter.append(hash[i])return hash_filterdef partitionLabels(self, s):res = []hash = self.countLabels(s)hash.sort(key=lambda x: x[0]) # 按左边界从小到大排序rightBoard = hash[0][1] # 记录当前区间的最大右边界leftBoard = 0for i in range(1, len(hash)):if hash[i][0] > rightBoard: # 如果出现分割点,将当前区间长度加入结果中,并更新左边界res.append(rightBoard - leftBoard + 1)leftBoard = hash[i][0]rightBoard = max(rightBoard, hash[i][1]) # 更新当前区间的最大右边界res.append(rightBoard - leftBoard + 1) # 将最后一个区间的长度加入结果中return res
复杂度分析:
- 时间复杂度:O(n)
- 空间复杂度:O(1),使用的hash数组是固定大小