763. 划分字母区间
一、题目难度
中等
二、相关标签与相关企业
[相关标签]
[相关企业]
三、题目描述
给你一个字符串 s
。我们要把这个字符串划分为尽可能多的片段,同一字母最多出现在一个片段中。
注意,划分结果需要满足:将所有划分结果按顺序连接,得到的字符串仍然是 s
。
返回一个表示每个字符串片段的长度的列表。
四、示例
示例1
输入:s = "ababcbacadefegdehijhklij"
输出:[9, 7, 8]
解释:
划分结果为“ababcbaca
”、“defegde
”、“hijhklij
”,每个字母最多出现在一个片段中。
像“ababcbacadefegde
”,“hijhklij
”这样的划分是错误的,因为划分的片段数较少。
示例2
输入:s = "eccbbbbdec"
输出:[10]
五、提示
1 <= s.length <= 500
s
仅由小写英文字母组成
贪心算法
- 思路:
- 首先,我们先遍历一次字符串,用字典
last_s = { }
记录每个字母最后出现的位置。 - 然后,我们再次从头遍历字符串,设定一个当前片段起
start
末end
位置,后续不断更新维护该片段。- 起
start
末end
都初始化为0
- 遍历过程中,对于每个字符,更新
end
为当前字符最后出现位置的最大字符 - 当遍历到
i
位置等于end
时,说明当前片段已经包含了所有出现过的字母 - 将当前片段的长度
end - start +1
添加到结果列表中 - 更新
start
为i + 1
,开始下一个片段的划分
- 起
- 首先,我们先遍历一次字符串,用字典
——————————————————————————————————————————————
如何实现用字典
和enumerate()函数
记录最后出现的位置??
1. enumerate
函数**
- 功能:
enumerate
是 Python 中的一个内置函数,它用于在遍历可迭代对象(如列表、字符串等)时,同时返回元素的索引和元素本身。它的基本语法是enumerate(iterable, start=0)
,其中iterable
是要遍历的可迭代对象,start
是可选参数,用于指定索引的起始值,默认是0
。
- 用法:
- 当执行
for i, char in enumerate(s):
时,对于字符串s
中的每个字符char
和i
,enumerate
会依次获取到该字符在字符串中的索引(从 0 开始,因为默认 start=0),而char
则获取到对应的字符本身。例如,如果s = "abc"
,那么第一次循环时,i
会是0
,char
会是a
;第二次循环时,i
会是1
,char
会是b
;第三次循环时,i
会是2
,char
会是c
。
- 当执行
以下是对这段代码中字典 last_index
以及 enumerate
函数相关操作的详细解释:
2. 字典 last_index
-
注意字典写入方法:
last_index={}
# 创建字典last_index[a] = 1
# 写入键值对- ——>
last_index = {'a' : 1}
-
功能及初始化:
- 字典是Python中的一种数据结构,它以键值对的形式存储数据,其中键是唯一的,通过键可以快速访问到对应的 值。在这里,
last_index
字典被初始化为一个空字典{}
,它的作用是用于记录每个字母在字符串s
中最后出现的位置。
- 字典是Python中的一种数据结构,它以键值对的形式存储数据,其中键是唯一的,通过键可以快速访问到对应的 值。在这里,
-
记录最后出现位置的原理:
- 在循环
for i, char in enumerate(s):
中,每次遍历到一个字符char
时,我们就将这个字符作为键,将它当前的索引i
作为值,存入last_index
字典中。关键在于,由于我们是按照字符串的顺序依次遍历的,所以当遇到同一个字母多次出现时,后面出现的位置索引会覆盖前面的记录。这样,当整个字符串遍历完成后,字典last_index
中每个字母对应的键值对中,值就是该字母在字符串中最后出现的位置。
- 在循环
-
写入后字典的形式示例:
- 假设字符串
s = "ababcbacadefegdehijhklij"
,经过上述循环遍历后,last_index
字典的形式可能如下:
- 假设字符串
{'a': 8,'b': 5,'c': 7,'d': 14,'e': 15,'f': 11,'g': 13,'h': 19,'i': 22,'j': 23,'k': 20,'l': 21
}
可以看到,每个字母作为键,其最后出现的位置作为值被准确地记录在了字典中。这样,在后续处理划分字母区间的过程中,我们就可以方便地通过查询这个字典来获取每个字母的最后出现位置信息,以便确定每个片段的范围。
class Solution:def partitionLabels(self, s: str) -> List[int]:# 1. 统计每个字符最后出现的位置(用到enumerate迭代器,返回字符串的索引和字符)# 初始化字典last_indexlast_index = {}# 从头遍历每个字符char出现的位置,由于从头向后遍历,后面的位置会覆盖前面的,for i, char in enumerate(s):last_index[char] = i# 2. 从头遍历字符串s的字符char,# 初始化起末位置start = 0end = 0# 初始化结果列表res = []# 再次遍历,对于每个字符char,索引ifor i, char in enumerate(s):# 更新end,为当前字符char最后出现的位置(字典中存着)end = last_index[char]if i == end:res.append(end- start + 1)return res
————————————————————————————————————————————
执行出错
0 / 118 个通过的测试用例
IndentationError: unindent does not match any outer indentation level^start = 0
Line 12 (Solution.py)
以上代码错误,是我顺着之前的算法步骤写的!
问题出在哪?
太粗心了呀!!!
- 首先,
i
碰到一次end
,需要更新start
位置为i+1
if i == end:res.append(end- start + 1)start = i + 1
一个很关键的点,下面来详细解释一下为什么是 end = max(end, last_index[char])
而不是直接 end = last_index[char]
:
理解需求
我们的目标是划分字符串 s
为尽可能多的片段,使得同一字母最多出现在一个片段中。在遍历字符串的过程中,要确定每个片段的结束位置 end
,这个位置应该是当前片段内所有字母最后可能出现的最远位置。
直接赋值的问题(end = last_index[char]
)
如果按照 end = last_index[char]
来更新 end
,那么每次遇到一个字符 char
,就会直接将 end
设置为该字符在字典中记录的最后出现位置。这样做会导致一个问题,就是可能会把 end
的值设置得过小,无法包含当前片段内其他字母后续可能出现的位置。
例如,对于字符串 s = "ababcbacadefegdehijhklij"
,假设我们已经开始划分第一个片段,初始时 end = 0
。当我们遍历到字符 a
时,如果按照直接赋值的方式,end
就会被设置为 a
的最后出现位置,也就是 8
。但此时,在后续的字符串中,还有其他字母(如 b
、c
等)在这个片段内也会出现,并且它们的最后出现位置可能比 8
更远。如果就这样把 end
固定为 8
,那么就无法正确划分出满足条件的片段,可能会导致某些字母被错误地划分到了下一个片段中,违背了“同一字母最多出现在一个片段中”的要求。
取最大值的好处(end = max(end, last_index[char])
)
而使用 end = max(end, last_index[char])
来更新 end
,就可以避免上述问题。每次遇到一个字符 char
,我们会比较当前的 end
值和该字符 char
的最后出现位置(last_index[char]
)。
- 如果
last_index[char]
大于当前end
值,说明该字符的最后出现位置更远,那么就需要把end
更新为这个更远的位置,以确保end
能够包含该字符以及可能后续出现的其他字母的最远位置。 - 如果
last_index[char]
小于等于当前end
值,说明当前end
的值已经能够涵盖该字符后续可能出现的位置了,不需要对end
进行更新。
继续以上面的字符串为例,初始时 end = 0
,当遍历到字符 a
时,假设 a
的最后出现位置是 8
,此时 end = max(0, 8)
,end
就会被更新为 8
。接着当遍历到字符 b
时,假设 b
的最后出现位置是 5
,因为 5
小于当前的 end
(也就是 8
),所以 end
保持不变,还是 8
。但当遍历到字符 c
时,假设 c
的最后出现位置是 7
,此时 end = max(8, 7)
,end
依然保持为 8
。这样,通过不断地取最大值来更新 end
,就能保证 end
始终是当前片段内所有字母最后可能出现的最远位置,从而正确地划分出满足条件的片段。
所以,不能简单地为每一个 char
都直接赋予 end
,而是要通过取最大值的方式来更新 end
,以确保划分片段的正确性。
修改了代码:
class Solution:def partitionLabels(self, s: str) -> List[int]:# 1. 统计每个字符最后出现的位置(用到enumerate迭代器,返回字符串的索引和字符)# 初始化字典last_indexlast_index = {}# 从头遍历每个字符char出现的位置,由于从头向后遍历,后面的位置会覆盖前面的,for i, char in enumerate(s):last_index[char] = i# 2. 从头遍历字符串s的字符char,# 初始化起末位置start = 0end = 0# 初始化结果列表res = []# 再次遍历,对于每个字符char,索引ifor i, char in enumerate(s):# 更新end,为当前字符char最后出现的位置(字典中存着)# 错:end = last_index[char]# 改:end = max(last_index[char], end)if i == end:res.append(end- start + 1)# 错,加:start = i + 1 return res
————————————————————————————————————————————————————————
IndentationError: unindent does not match any outer indentation level^start = 0
Line 12 (Solution.py)
检查半天原来是缩进问题,缩进是python的特色。
class Solution:def partitionLabels(self, s: str) -> List[int]:# 1. 统计每个字符最后出现的位置(用到enumerate迭代器,返回字符串的索引和字符)# 初始化字典last_indexlast_index = {}# 从头遍历每个字符char出现的位置,由于从头向后遍历,后面的位置会覆盖前面的,for i, char in enumerate(s):last_index[char] = i# 2. 从头遍历字符串s的字符char,# 初始化起末位置start = 0end = 0# 初始化结果列表res = []# 再次遍历,对于每个字符char,索引ifor i, char in enumerate(s):# 更新end,为当前字符char最后出现的位置(字典中存着)# 错:end = last_index[char]# 改:end = max(last_index[char], end)if i == end:res.append(end- start + 1)# 错,加:start = i + 1 return res
————————————————————————————
通过