Offer 驾到,掘友接招!我正在参与2022春招打卡活动,点击查看活动详情。
2030. 含特定字母的最小子序列
思路
首先明确字典序:以两个string A B为例,先从前往后逐位比较,找到最先不同的位,较小者在前,若其中之一到达末尾,则长度较短者在前。 求字典序最小的子序列,需要维护不同下标之间的相对大小关系,且求不必连续的子序列,则很容易想到用单调栈维护。套用单调栈模板,进一步思考栈顶元素弹出的条件。不难想到有以下几点:
- 栈顶元素大于当前元素,则用当前元素替换栈顶元素一定可以减小结果的字典序
- 栈中元素加未遍历元素总长度需大于给定长度,方可弹出栈顶元素
- 当栈顶元素是特定字母时,栈中特定字母数量加未遍历元素中特定字母数量需大于给定重复数字方可弹出栈顶元素
通过上述方法,从栈底到栈顶就得到了一个长度一定大于等于要求的子序列,且栈内元素尽可能是只升不减的。我们再遍历一下栈内元素,综合考虑特定字母的重复要求,选择靠前的元素组成给定长度的子序列即可。
这类带限制条件的单调栈细节比较多,需要注意
Code
class Solution {
public:
string smallestSubsequence(string s, int k, char let, int r) {
int n = size(s), u = 0;
vector<int> nums(n);
//统计以i开头的子串中特定字母的数量
for(int i = n - 1; i >= 0; i --) {
if(s[i] == let) u ++;
nums[i] = u;
}
vector<char> st;
int cnt = 0;
for(int i = 0; i < n; i ++) {
int len = n - i;
//满足三种条件即可弹出栈顶元素
while(st.size() > max(0, k - len) && st.back() > s[i] && [&]{
if(st.back() != let) return true;
return cnt + nums[i] > r;
}()) {
if(st.back() == let) cnt --;
st.pop_back();
}
if(s[i] == let) cnt ++;
st.push_back(s[i]);
}
string res;
cnt = 0, n = size(st);
//遍历栈内元素,得到目标串
for(int i = 0; i < st.size(); i ++) {
if(st[i] == let && cnt < r) res += st[i], cnt ++;
else if(res.size() + r - cnt < k) res += st[i];
}
return res;
}
};
复杂度分析
遍历原串一次加遍历栈内元素一次,线性复杂度。
- 时间复杂度
- 空间复杂度
欢迎讨论指正