LeetCode每日一题2025-06-06
2434. 使用机器人打印字典序最小的字符串 M
给你一个字符串 s 和一个机器人,机器人当前有一个空字符串 t 。执行以下操作之一,直到 s 和 t 都变成空字符串:
- 删除字符串
s的 第一个 字符,并将该字符给机器人。机器人把这个字符添加到t的尾部。 - 删除字符串
t的 最后一个 字符,并将该字符给机器人。机器人将该字符写到纸上。
请你返回纸上能写出的字典序最小的字符串。
示例 1:
输入:s = “zza”
输出:“azz”
解释:用 p 表示写出来的字符串。
一开始,p=“” ,s=“zza” ,t=“” 。
执行第一个操作三次,得到 p=“” ,s=“” ,t=“zza” 。
执行第二个操作三次,得到 p=“azz” ,s=“” ,t=“” 。
示例 2:
输入:s = “bac”
输出:“abc”
解释:用 p 表示写出来的字符串。
执行第一个操作两次,得到 p=“” ,s=“c” ,t=“ba” 。
执行第二个操作两次,得到 p=“ab” ,s=“c” ,t=“” 。
执行第一个操作,得到 p=“ab” ,s=“” ,t=“c” 。
执行第二个操作,得到 p=“abc” ,s=“” ,t=“” 。
示例 3:
输入:s = “bdda”
输出:“addb”
解释:用 p 表示写出来的字符串。
一开始,p=“” ,s=“bdda” ,t=“” 。
执行第一个操作四次,得到 p=“” ,s=“” ,t=“bdda” 。
执行第二个操作四次,得到 p=“addb” ,s=“” ,t=“” 。
提示:
1 <= s.length <= 10⁵s只包含小写英文字母。
问题分析
如何通过将字符串 中的字符依次移入一个栈 ,然后从栈 中弹出字符构建最终字符串 ,使得 的字典序最小。操作包括:
- 从 的头部取出字符追加到 的尾部(栈顶)。
- 从 的尾部(栈顶)取出字符追加到 的尾部。
目标是最小化最终的字符串 。我们希望尽早地将较小的字符放入 。
算法思路
这是一个贪心问题。在任何时候,我们都可以选择从 中取下一个字符放入栈 ,或者从栈 中取出栈顶字符放入 (如果 不为空)。为了使 字典序最小,我们应该优先将当前可用的最小字符放入 。当前可用的字符有两个来源:栈顶的字符 和 中剩余的所有字符。
从 中取出的字符必须先经过栈 才能进入 。因此,我们能直接放入 的只有栈顶字符 。
关键的决策点在于:当前栈顶字符 是否应该立即放入 ?
如果 大于等于 中剩余字符的最小者,那么如果现在放入 ,可能会导致后面更小的字符被压在栈底,或者不得不将这个相对较大的字符提前放入 。在这种情况下,更好的策略是先将 中更小的字符移入栈 ,希望它们能在某个时候被弹出。
如果 小于等于 中剩余字符的最小者,那么 是当前所有可用的字符(栈顶和 中剩余的)中最小的(或之一)。此时,应该立即将 放入 ,因为它是当前最优的选择。
基于此,贪心策略如下:
- 预先计算出 的每一个后缀的最小字符。设 是 的最小字符。
- 遍历 的字符,依次将其推入栈 。在每次推入之前(或之后),检查栈顶字符 。
- 如果栈 不为空,并且栈顶字符 小于等于当前 中剩余字符的最小者(即 ,其中 是 中下一个要处理的字符索引),则重复将栈顶字符弹出并追加到 中,直到栈为空或栈顶字符大于 。
- 将 的当前字符推入栈 。
- 当 中的所有字符都已处理完毕后,将栈 中剩余的所有字符依次弹出并追加到 中。
时间复杂度
- 预计算后缀最小值:需要遍历字符串 一次,时间复杂度为 ,其中 是字符串 的长度。
- 模拟机器人操作:
- 每个字符从 被推入栈 一次。
- 每个字符最多从栈 被弹到 一次。
- 栈的推入和弹出操作总次数是 。
- 追加到 的操作总次数是 。
- 内层 while 循环的总执行次数(所有外层循环迭代加起来)不会超过栈的总弹出次数,即 。
总的时间复杂度为 。
代码分解
- 预计算后缀最小值: 创建一个与 同等长度的列表
min_suffix。从后向前填充,min_suffix[i]存储 的最小字符。1
2
3
4min_suffix = [None] * n
min_suffix[n - 1] = s[n - 1]
for i in range(n - 2, -1, -1):
min_suffix[i] = min(s[i], min_suffix[i + 1]) - 初始化: 创建一个空列表
t作为栈,一个空列表p用于存储结果字符,以及一个指针s_ptr初始化为 0 指向 的头部。1
2
3t = []
p = []
s_ptr = 0 - 主循环: 当
s_ptr小于 时,表示 中还有字符未处理。1
2
3while s_ptr < n:
# ... 处理逻辑 ...
s_ptr += 1 # 移动指针到 s 的下一个字符 - 贪心弹出: 在主循环内部,获取当前 中剩余字符的最小者
current_min_in_s = min_suffix[s_ptr]。只要栈t不为空且栈顶字符小于等于current_min_in_s,就将栈顶字符弹出并追加到p中。1
2
3current_min_in_s = min_suffix[s_ptr]
while t and t[-1] <= current_min_in_s:
p.append(t.pop()) - 推入栈: 将 推入栈
t。1
t.append(s[s_ptr])
- 处理剩余栈: 主循环结束后, 中的所有字符都已进入或经过栈 。将栈 中剩余的所有字符依次弹出并追加到 中。
1
2while t:
p.append(t.pop()) - 构建结果: 将
p中的字符连接成最终字符串。1
return "".join(p)
代码实现
1 | class Solution: |
