316. 去除重复字母

发布时间 2023-10-08 10:06:23作者: BJFU-VTH

链接

https://leetcode.cn/problems/remove-duplicate-letters/description/

思路

这个题并不是传统的单调栈,所以硬套单调栈会懵逼。

什么时候用单调栈? 这个题目要求去除重复字母,还要保持字典序。 注意,跟相对顺序相关的题目,如:其后比他大的第一个元素,其后比他小的第一个元素,再比如保持字典序的题目,都适合用单调栈。

应用单调栈我们要想清楚的核心问题是:

1. 什么情况下我们弹栈。

2. 栈内元素是(部分)升序还是(部分)降序

3. 我们应该从前往后遍历,还是从后往前遍历

在这个题目中,我们要的是保持字典序,即我们要的最后的序列是升序序列。所以我们解答了第二个核心问题,即我们要的是升序。

在这个题目中,还有个条件是让我们去除重复字母。我们要的是升序,所以可能要把比较大的字母弹栈。比较大的字母越早被弹栈,我们越能保持字典序。(举个例子,dabd,我们应该让第一个d弹栈),所以我们应该从前往后遍历。

在这个题目中,我们弹栈也有两种情况。 既然我们要保持字典序,那应该栈顶元素大于待入栈元素时弹栈,但并不是每次都这样操作。因为如果栈顶元素没有出现在剩余的未遍历序列中,我们就不能再弹了,再弹我们就漏字母了,所以这里要加一个判断。

代码

class Solution:
    def removeDuplicateLetters(self, s: str) -> str:
        # visited为True表示该字母已经加入单调栈中了
        visited = [False for i in range(26)]
        # remains代表当前未遍历到的字母的计数(<=0代表未遍历到的序列中没有这个字母了)
        remains = [0 for i in range(26)]
        single_stack = []
        # 初始化remains
        for i in s:
            idx = ord(i) - ord('a')
            remains[idx] += 1
        for i in s:
            if not visited[ord(i)-ord('a')]:
                # 如果这个字符没有加入到单调栈中
                while single_stack and ord(single_stack[-1]) > ord(i):
                    idx = ord(single_stack[-1])-ord('a')
                    if remains[idx] > 0:
                        # 还有剩余的大字符, 影响字典序,弹栈并且重置加入单调栈的状态
                        visited[idx] = False
                        single_stack.pop()
                    # 必须要有else, 否则remains[idx]没有剩余数值时,单调栈不弹栈, 会陷入死循环
                    else:
                        break
                # 别管有没有弹栈, 当前字母都得入栈, 跟其他单调栈的题目没啥区别
                single_stack.append(i)
                # 入单调栈了, 自然是要更新visited记录
                visited[ord(i) - ord('a')] = True
            # remains是剩余序列的计数, 已经遍历过当前字母了, 所以要减1
            remains[ord(i)-ord('a')] -= 1
        return ''.join(single_stack)