Using a Robot to Print the Lexicographically Smallest String

Using a Robot to Print the Lexicographically Smallest String

You are given a string s and a robot that currently holds an empty string t . Apply one of the following operations until s and t are both empty:

  • Remove the first character of a string s and give it to the robot. The robot will append this character to the string t.
  • Remove the last character of a string t and give it to the robot. The robot will write this character on paper.

Return the lexicographically smallest string that can be written on the paper.

Example 1:

Input: s = "zza"
Output: "azz"
Explanation: Let p denote the written string.
Initially p="", s="zza", t="".
Perform first operation three times p="", s="", t="zza".
Perform second operation three times p="azz", s="", t="".

Example 2:

Input: s = "bac"
Output: "abc"
Explanation: Let p denote the written string.
Perform first operation twice p="", s="c", t="ba". 
Perform second operation twice p="ab", s="c", t="". 
Perform first operation p="ab", s="", t="c". 
Perform second operation p="abc", s="", t="".

Example 3:

Input: s = "bdda"
Output: "addb"
Explanation: Let p denote the written string.
Initially p="", s="bdda", t="".
Perform first operation four times p="", s="", t="bdda".
Perform second operation four times p="addb", s="", t="".

Constraints:

$1 \leq s.length \leq {10}^{5}$
s consists of only English lowercase letters.

 

解题思路

  可以发现字符串$t$的操作就是一个栈。因为是字典序最小,所以很容易想到先从$s$里面把所有的字母$a$挑出来,具体做法是先在$s$中找到字母$a$最后出现的位置$p[a]$,然后从前往后枚举到$p[a]$,如果遇到$a$,则直接把$a$输出,否则遇到其他字母则压入栈中。

  然后就是字母$b$,此时接着从上次遍历之后的位置$p[a]+1$开始,直到遍历到字母$b$在$s$中最后出现的位置$p[b]$。在这之前需要将栈顶连续的部分中小于等于字母$b$的字符输出,然后再遍历$s$,如果遇到$b$,则直接把$b$输出,否则遇到其他字母则压入栈中。

  之后的字母以此类推。因此具体流程是枚举字母$a \sim z$,对于某个字母先将栈顶元素中小于等于该字母的元素弹出,然后从上一次的位置开始遍历$s$,如果遇到该字母则直接输出,否则压入栈中,直到遍历到该字母在$s$中出现的最后一个位置。

  AC代码如下:

 1 class Solution {
 2 public:
 3     string robotWithString(string s) {
 4         int n = s.size();
 5         vector<int> p(26, -1);
 6         for (int i = 0; i < n; i++) {
 7             p[s[i] - 'a'] = i;
 8         }
 9         string ans, t;
10         for (int i = 0, j = 0; i < 26; i++) {
11             while (!t.empty() && t.back() <= 'a' + i) {
12                 ans += t.back();
13                 t.pop_back();
14             }
15             while (j < n && j <= p[i]) {
16                 if (s[j] == 'a' + i) ans += s[j];
17                 else t += s[j];
18                 j++;
19             }
20         }
21         reverse(t.begin(), t.end());
22         return ans + t;
23     }
24 };

  当时比赛的时候想到的思路是,每当把一个元素压入栈时有两种选择,一种是出栈顶元素,另一种不弹出栈顶元素继续往后遍历。贪心的思路容易想到如果此时栈顶元素小于$s$后面剩余的字符,那么就应该把栈顶元素弹出。所有容易想到预处理出来每个位置后面有多少个字符是小于当前位置的字符的,每次弹出元素时,就把栈中大于栈顶元素的字符所对应的位置减$1$。然后发现这个信息并不好维护,基本只能暴力去枚举,所以比赛时没做出来。

  实际上问题的本质是看一下此时栈顶元素所对应的位置是不是对应后缀的最小元素,如果是则弹出栈顶元素。因此实际上只需预处理一个后缀最小值即可。

  AC代码如下:

 1 class Solution {
 2 public:
 3     string robotWithString(string s) {
 4         int n = s.size();
 5         vector<char> p(n + 1, 'z');
 6         for (int i = n - 1; i >= 0; i--) {
 7             p[i] = min(p[i + 1], s[i]);
 8         }
 9         string ans;
10         vector<int> t;
11         for (int i = 0; i < n; i++) {
12             t.push_back(i);
13             while (!t.empty() && s[t.back()] <= p[i + 1]) {
14                 ans += s[t.back()];
15                 t.pop_back();
16             }
17         }
18         return ans;
19     }
20 };

 

参考资料

  力扣第314场周赛:https://www.bilibili.com/video/BV1KR4y1R7vF/

posted @ 2022-10-09 23:28  onlyblues  阅读(43)  评论(0编辑  收藏  举报
Web Analytics