018数据结构之队列——算法备赛
移掉k位数字
给你一个以字符串表示的非负整数 num
和一个整数 k
,移除这个数中的 k
位数字,使得剩下的数字最小。请你以字符串形式返回这个最小的数字。
注意:输出不能有任何前导零,给定的num不包含前导0。
从原数字移除所有的数字,剩余为空就是 0 。
原题链接
思路分析
判断两个正整数的大小关系,先看位数,位数相同,从左往右看第一个不同的数字,该位数字小的则小,例123467<123589;
题目说移掉k位数字,可以看成选择 t =(s-k)(s为字符串长度)位数字组成的最小数值。
我们可以遍历字符串,每遍历到一位判断是否保留或移除,关键问题是如何决策是否保留?
我们将保留的数字尾插在一个双端队列中,队列中的每一个数对应所求的每一位,每次遍历到一位数字,将它与队列尾部的数字比较,较小的话,队列尾删,直到该数字大于队列尾部,入队。这样做可以确保高位数字尽量小,同时要保证队列长度符合t。
注:选用双端队列作为解题的数据结构而不选用栈的原因:将容器中数据转换为目标字符串时更方便,(栈顶——>栈底)=(低位数字——>高位数字)。
代码
string removeKdigits(string num, int k) {int s1=num.size();int s=s1-k;string tar;if(!s) return "0";deque<char>st;for(int i=0;i<s1;i++){//s-st.size()表示队列剩余空间,s1-i表示字符串剩余长度while(!st.empty()&&num[i]<st.back()&&s-st.size()<s1-i){st.pop_back();}if(st.size()<s)st.push_back(num[i]);}while(!st.empty()){if(!tar.size()&&st.front()=='0'){st.pop_front(); continue;} //去除前导0tar.push_back(st.front());st.pop_front();}if(!tar.size()) return "0"; //为空置为“0”return tar;
}
删除字符串中所有的相邻重复项
问题描述
给你一个字符串 s
,「k
倍重复项删除操作」将会从 s
中选择 k
个相邻且相等的字母,并删除它们,使被删去的字符串的左侧和右侧连在一起。
你需要对 s
重复进行无限次这样的删除操作,直到无法继续为止。
在执行完所有删除操作后,返回最终得到的字符串。
本题答案保证唯一。
原题链接
思路分析
使用栈模拟删除的操作,使用队列返回最终得到的字符串,所以选择双端队列。
代码
string removeDuplicates(string s, int k) {deque<pair<char,int>>dq;for(char ch:s){ if(dq.empty()){dq.push_back({ch,1});}else{auto&[key,val] = dq.back();if(key==ch){val++;if(val==k) dq.pop_back(); //k个重复项删除}else{dq.push_back({ch,1});}}}string ans;while(!dq.empty()){ //从头到尾返回结果字符串auto [key,val] =dq.front();ans+=string(val,key);dq.pop_front();}return ans;
}
知道秘密的人
问题描述
在第 1
天,有一个人发现了一个秘密。
给你一个整数 delay
,表示每个人会在发现秘密后的 delay
天之后,每天 给一个新的人 分享 秘密。同时给你一个整数 forget
,表示每个人在发现秘密 forget
天之后会 忘记 这个秘密。一个人 不能 在忘记秘密那一天及之后的日子里分享秘密。
给你一个整数 n
,请你返回在第 n
天结束时,知道秘密的人数。由于答案可能会很大,请你将结果对 109 + 7
取余 后返回。
2 <= n <= 1000
1 <= delay < forget <= n
原题链接
思路分析
初步设想:可以设计一个双端队列,每天将遗忘秘密的人从头部出队,新发现秘密的人从尾部入队,最后队列中剩余的总人数就是答案。
初步设想其实是维护了两个临界点:遗忘秘密的临界点front
和发现秘密的临界点back
。这是一个大致的想法,还有一个问题是每个人要发现秘密delay
天后才能分享秘密,所以还需维护一个可以分享秘密的临界点valid
。为了实现这三个临界点的特殊队列,我们可以用一个数组来自己实现:
最后[front,back]
区间内的总人数就是答案。因为三个指针每次移动的步长是一样的,可以用ans
和sum
来动态地维护[front,back]
和[front,valid)
内的总人数。
代码
int peopleAwareOfSecret(int n, int delay, int forget) {const int MOD = 1e9+7;int ans = 1,sum = 0;vector<int>q; //数组模拟队列q.push_back(1);int front=0; //首尾指针int valid = front; //[front,valid)范围内的人可分享秘密for(int i=2;i<=n;i++){if(i>delay){sum = (sum+q[valid++])%MOD; //可分享秘密的人数增加}if(i>forget){ans = (ans-q[front]+MOD)%MOD; //知道秘密的总人数减少sum = (sum-q[front++]+MOD)%MOD; //可分享秘密的人减少}q.push_back(sum); //新增发现秘密的人ans = (ans+sum)%MOD; //更新答案}return ans;
}