去除重复字母

力扣(Leetcode)相关题目链接:

316. 去除重复字母 - 力扣(LeetCode)

给你一个字符串 s ,请你去除字符串中重复的字母,使得每个字母只出现一次。

需保证返回结果的字典序最小(要求不能打乱其他字符的相对位置)。

关键点:
  1. 每个字母只能出现一次
  2. 字典序最小:就是要确保最终结果尽量按照字母顺序排列(如 'a' < 'b' < 'c')。

解题思路:

我们使用单调栈和贪心策略来解决问题。通过不断检查当前字符和栈顶字符,决定是否弹出栈顶字符来获得更小的字典序。

算法步骤:

  1. 记录每个字母最后出现的位置

    我们需要知道某个字母是否还会在后面出现,以便决定是否可以移除当前栈顶字符并在后面重新添加它。
  2. 使用栈维护一个字典序最小的序列

    使用栈来存储处理后的字符,栈中的字符始终是去重的,并且字典序最小。
  3. 贪心策略

    如果当前字符比栈顶字符字典序小,并且栈顶字符在后面还会出现,那么我们可以弹出栈顶字符以保证字典序更小。

逐步推导示例:

我将以字符串 s = "cbacdcbc" 为例,详细推导过程。

  1. 初始化

    • 记录每个字符的最后出现位置:

      last_occurrence = {'c': 7, 'b': 6, 'a': 2, 'd': 4}

    • 定义一个栈 stack 和一个集合 seen,用来存储栈中的字符是否已经存在。
    • 集合seen是必要的吗?仅仅使用栈本身来判断字符是否已经在栈中也可以,但这需要遍历整个栈来检查(时间复杂度为 O(n))。使用 seen 集合 可以将字符是否已经存在的判断操作从 O(n) 降低到 O(1)集合的查询时间复杂度为 O(1))。
  2. 逐字符处理

Step 1: 处理字符 'c'
  • 栈是空的,直接将 'c' 入栈。
  • stack = ['c']
  • seen = {'c'}
Step 2: 处理字符 'b'
  • 'b' 小于栈顶字符 'c',并且 'c' 之后还会出现(last_occurrence['c'] = 7)。
  • 因此,我们弹出栈顶的 'c',将 'b' 入栈。
  • stack = ['b']
  • seen = {'b'}
Step 3: 处理字符 'a'
  • 'a' 小于栈顶字符 'b',并且 'b' 之后还会出现(last_occurrence['b'] = 6)。
  • 因此,我们弹出栈顶的 'b',将 'a' 入栈。
  • stack = ['a']
  • seen = {'a'}
Step 4: 处理字符 'c'
  • 'c' 大于栈顶字符 'a',可以直接入栈。
  • stack = ['a', 'c']
  • seen = {'a', 'c'}
Step 5: 处理字符 'd'
  • 'd' 大于栈顶字符 'c',可以直接入栈。
  • stack = ['a', 'c', 'd']
  • seen = {'a', 'c', 'd'}
Step 6: 处理字符 'c'
  • 'c' 已经在栈中(seen 集合里),直接跳过。
Step 7: 处理字符 'b'
  • 'b' 小于栈顶字符 'd',但 'd' 之后不会再出现了(last_occurrence['d'] = 4)。
  • 所以不能弹出栈顶元素'd'直接将 'b' 入栈。
  • stack = ['a', 'c', 'd', 'b']
  • seen = {'a', 'c', 'd', 'b'}
Step 8: 处理字符 'c'
  • 'c' 已经在栈中,直接跳过。

最终栈中的字符:

  • stack = ['a', 'c', 'd', 'b']
  • 最终的结果是 'acdb'

 完整代码

栈+集合(哈希表)版本
class Solution:
    def removeDuplicateLetters(self, s: str) -> str:
        # 记录每个字符最后出现的位置
        last_occurrence = {char: i for i, char in enumerate(s)}
        stack = []  # 单调栈
        seen = set()  # 用于判断字符是否已经在栈中
        
        for i, char in enumerate(s):
            # 如果字符已经在栈中,跳过
            if char in seen:
                continue
            
            # 栈顶字符字典序大于当前字符,并且栈顶字符在后面还会出现,移除栈顶字符
            # last_occurrence[stack[-1]] 表示栈顶元素最后出现的位置
            while stack and char < stack[-1] and i < last_occurrence[stack[-1]]:
                seen.remove(stack.pop())
            
            # 将当前字符压入栈,并加入到 seen 集合中
            stack.append(char)
            seen.add(char)
        
        # 最终栈中的字符即为答案,转为字符串返回
        return ''.join(stack)
 只要栈的版本
class Solution:
    def removeDuplicateLetters(self, s: str) -> str:
        # 记录每个字符最后出现的位置
        last_occurrence = {char: i for i, char in enumerate(s)}
        stack = []  # 单调栈
        
        for i, char in enumerate(s):
            # 如果字符已经在栈中,跳过(通过栈检查)
            if char in stack:
                continue
            
            # 栈顶字符字典序大于当前字符,并且栈顶字符在后面还会出现,移除栈顶字符
            while stack and char < stack[-1] and i < last_occurrence[stack[-1]]:
                stack.pop()
            
            # 将当前字符压入栈
            stack.append(char)
        
        # 最终栈中的字符即为答案,转为字符串返回
        return ''.join(stack)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Wαyne_127

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值