316. 去除重复字母

贪心+栈

字符串里面每个字母至少出现1次,有些字母会出现多次,对于重复的字母,我们只能保留1次,去掉多余的字母,合理的选取要保留的元素,使得重构后的字符串字典序最小

对于bcabc,里面b和c重复,a只出现1次,去掉前面重复的bc,得到abc字典序最小
对于cbacdcbc,其中cb重复,ad唯一,我们ad的相对位置一定是不能改变的,整个字符串现在变成xxaxdxxx,往里面填充字符,显然xxacdxbxacbd是最终答案

经过初步分析,我们现在得到字符串里面字母个数的两个特性

  1. 字母个数只有一个
  2. 字母个数有多个

对于个数只有一个的字母,无论如何都要保留下来,而且它与其它唯一的字母相对位置是不变的,就像例2一样
而考虑个数多个的字母,我们只保留一个

	counter = Counter(s)

现在我们能够根据出现次数来区分字母了,然后我们要做的就是构造字符串了

首先我们肯定需要一个容器来帮助我们重构字符串的,考虑队列 or 栈,因为我们想一趟遍历就能解决这个问题,但是具体用哪个容器,还是要分析一下

遍历字符串,对于里面的每个字符,如果当前元素个数唯一,则直接加入容器中
如果这个元素不唯一,我们考虑贪心的策略,则应该和容器最后一个元素比较,即前一个加入容器的元素,即前一个元素为i,当前元素为j
那么有

  1. 如果i小于j,那么加入j
  2. 如果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来统计剩余的字母个数,每次入队的时候更新它
然后由于连续的重复元素,我们还需要判断该元素是否存在栈中,如果存在,那么我们不需要入队了,于此同时更新对应字母个数

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值