以下是 LeetCode 2030 的 Python3 实现,采用与 Java 版本相同的单调栈 + 贪心 + 后缀预处理策略。

class Solution:
    def smallestSubsequence(self, s: str, k: int, letter: str, repetition: int) -> str:
        n = len(s)
        
        # 后缀中 letter 的数量
        suffix = [0] * (n + 1)
        for i in range(n - 1, -1, -1):
            suffix[i] = suffix[i + 1] + (1 if s[i] == letter else 0)
        
        stack = []          # 模拟栈
        letter_in_stack = 0 # 栈中 letter 的数量
        
        for i, c in enumerate(s):
            # 尝试弹出栈顶以获得更小的字典序
            while stack and stack[-1] > c:
                top = stack[-1]
                rest = n - i          # 剩余可用的字符数(包括当前字符)
                
                if top == letter:
                    # 弹出 letter 后,能否满足长度和 letter 数量要求
                    if (letter_in_stack - 1 + suffix[i] >= repetition 
                            and len(stack) - 1 + rest >= k):
                        stack.pop()
                        letter_in_stack -= 1
                    else:
                        break
                else:
                    if len(stack) - 1 + rest >= k:
                        stack.pop()
                    else:
                        break
            
            # 当前字符入栈
            stack.append(c)
            if c == letter:
                letter_in_stack += 1
        
        # 截取前 k 个字符
        res = stack[:k]
        
        # 统计当前 letter 数量,若不足则从后向前修改非 letter 字符
        cnt = sum(1 for ch in res if ch == letter)
        need = repetition - cnt
        if need > 0:
            # 转为列表方便修改
            arr = list(res)
            for i in range(len(arr) - 1, -1, -1):
                if need == 0:
                    break
                if arr[i] != letter:
                    arr[i] = letter
                    need -= 1
            res = arr
        
        return ''.join(res)

思路解析

  1. 后缀预处理
    使用 suffix 数组记录每个位置之后(包括自身)的 letter 数量,用于快速判断弹出栈顶 letter 后,后续字符是否还能补齐 repetitionletter

  2. 单调栈贪心

    • 遍历字符串 s,维护一个单调递增栈(栈底到栈顶的字典序尽可能小)。
    • 当前字符 c 比栈顶小时,若弹出栈顶后仍能用剩余字符凑足长度 k 和所需 letter 数量,则弹出栈顶(让更小的字符占据更靠前的位置)。
    • 弹出判断需区分栈顶是否为 letter,因为涉及 letter 的数量约束。
  3. 截断与补足

    • 因栈可能大于 k,直接取前 k 个字符。
    • 若此时 letter 数量不足,从结果末尾开始,将非 letter 字符改为 letter。这样做对字典序的影响最小(因为修改发生在更靠后的位置)。

复杂度

  • 时间O(n),每个字符最多入栈、出栈一次。
  • 空间O(n),用于后缀数组和栈。
Logo

汇聚全球AI编程工具,助力开发者即刻编程。

更多推荐