贪心+栈
字符串里面每个字母至少出现1次,有些字母会出现多次,对于重复的字母,我们只能保留1次,去掉多余的字母,合理的选取要保留的元素,使得重构后的字符串字典序最小
对于bcabc
,里面b和c重复,a只出现1次,去掉前面重复的bc,得到abc
字典序最小
对于cbacdcbc
,其中cb重复,ad唯一,我们ad的相对位置一定是不能改变的,整个字符串现在变成xxaxdxxx
,往里面填充字符,显然xxacdxbx
即acbd
是最终答案
经过初步分析,我们现在得到字符串里面字母个数的两个特性
- 字母个数只有一个
- 字母个数有多个
对于个数只有一个的字母,无论如何都要保留下来,而且它与其它唯一的字母相对位置是不变的,就像例2一样
而考虑个数多个的字母,我们只保留一个
counter = Counter(s)
现在我们能够根据出现次数来区分字母了,然后我们要做的就是构造字符串了
首先我们肯定需要一个容器来帮助我们重构字符串的,考虑队列 or 栈,因为我们想一趟遍历就能解决这个问题,但是具体用哪个容器,还是要分析一下
遍历字符串,对于里面的每个字符,如果当前元素个数唯一,则直接加入容器中
如果这个元素不唯一,我们考虑贪心的策略,则应该和容器最后一个元素比较,即前一个加入容器的元素,即前一个元素为i,当前元素为j
那么有
- 如果i小于j,那么加入j
- 如果i大于j,那么考虑i的性质
2.1 如果i是唯一的,那么我们不能取出容器中的i,此时我们仍然考虑加入j
2.2 如果i不是唯一的,那么我们取出i,因为我们想要贪心的使得较小的元素尽可能的靠前
考虑到上面的操作,不断有放入、弹出的操作,这里用栈应该更合适,并且对于放入元素的队列,我们需要更新它的个数
class Solution:
def removeDuplicateLetters(self, s: str) -> str:
counter = Counter(s)
stack = [s[0]]
for i in range(1,len(s)):
ch = s[i]
# 非空,并且栈顶元素大于当前元素,而且它个数不为1
while stack != [] and stack[-1] > ch and counter[stack[-1]] != 0:
# 弹出
stack.pop()
# 此时要么栈为空,要么栈顶元素小于当前元素,要么栈顶元素个数为1
# 这三种情况我们都需要放入当前元素
stack.append(ch)
# 更新
counter[ch] -= 1
return ''.join(stack)
多出来一个c,原因是我们对已经在栈中的元素没有做判断,导致里面c重复了两次
现在我们的代码变为
class Solution:
def removeDuplicateLetters(self, s: str) -> str:
counter = Counter(s)
instack = dict()
stack = [s[0]]
instack[s[0]] = True
counter[s[0]] -= 1
for i in range(1,len(s)):
# print(stack,instack)
ch = s[i]
# 如果已经在栈中,跳过
if instack.get(ch,False) == True:
continue
# 非空,并且栈顶元素大于当前元素,而且它个数不为1
while stack != [] and stack[-1] > ch and counter[stack[-1]] != 0:
# 更新状态
instack[stack[-1]] = False
# 弹出
stack.pop()
# 此时要么栈为空,要么栈顶元素小于当前元素,要么栈顶元素个数为1
# 这三种情况我们都需要放入当前元素
stack.append(ch)
# 更新个数
counter[ch] -= 1
# 更新状态
instack[ch] = True
# print(instack)
return ''.join(stack)
对于这个样例出错了
原因显然是b我们对于栈中的元素忘记更新counter了
if instack.get(ch,False) == True:
counter[ch] -= 1
continue
下面是通过的代码
class Solution:
def removeDuplicateLetters(self, s: str) -> str:
counter = Counter(s)
instack = dict()
stack = [s[0]]
instack[s[0]] = True
counter[s[0]] -= 1
for i in range(1,len(s)):
# print(stack,instack)
ch = s[i]
# 如果已经在栈中,跳过
if instack.get(ch,False) == True:
counter[ch] -= 1
continue
# 非空,并且栈顶元素大于当前元素,而且它个数不为1
while stack != [] and stack[-1] > ch and counter[stack[-1]] != 0:
# 更新状态
instack[stack[-1]] = False
# 弹出
stack.pop()
# 此时要么栈为空,要么栈顶元素小于当前元素,要么栈顶元素个数为1
# 这三种情况我们都需要放入当前元素
stack.append(ch)
# 更新个数
counter[ch] -= 1
# 更新状态
instack[ch] = True
# print(instack)
return ''.join(stack)
两年前的题目终于通过了 😂
总结
一道贪心题目,贪心的思路就是:
每当碰到一个较小的当前字符,对于栈顶元素,如果它不唯一,那么反复弹出,然后加入当前字符
然后代码实现的时候,我们需要用一个dictcounter
来统计剩余的字母个数,每次入队的时候更新它
然后由于连续的重复元素,我们还需要判断该元素是否存在栈中,如果存在,那么我们不需要入队了,于此同时更新对应字母个数