算法刷题 | Leetcode 栈与队列 | 0117-0118
总结在前
一道面试题:栈里面的元素在内存中是连续分布的么?
- 栈是容器适配器,底层容器使用不同的容器,导致栈内数据在内存中是不是连续分布。
- 缺省情况下,默认底层容器是 deque,deque 的在内存中的数据分布是不连续的。
- 栈不提供迭代器,因为只能在栈顶进行操作。
栈代表题:20 有效的括号、1047 删除字符串中所有相邻的重复项、150 逆波兰表达式求值
队列代表题:239 滑动窗口最大值、347 前 K 个高频元素。
各种队列小览
- deque:双端队列
- 单调队列:指所含元素有序的队列
- priority_queue:优先队列:堆
栈专题
Leetcode 232. 用栈实现队列
3年前做过,用的是固定一个数组in操作push,一个数组out操作pop,每次操作后确保数据都在in中。
其实没有必要每次都把数据搬来搬去,因为操作都是有效的,不会出现栈为空进行pop或peek操作,那么可以在操作pop或peek时检查数据是否在out中,是则直接操作,不是则在in中,搬进out即可。
同理,操作push检查数据是否在out中,是则搬进in,这里判断 in 是否为空不太好,因为一开始为空的话 in 和 out 都为空,还得判断 out 是否为空,不如直接判断out是否为空。
class MyQueue {
public:
MyQueue() {
top = -1;
}
void push(int x) {
if(!out.empty()){ // 数据在out里,搬到in里入队
while(!out.empty()){
in.push(out.top());
out.pop();
}
}
in.push(x);
top++;
}
int pop() {
if(out.empty()){ // out为空,数据在in里,搬到out中pop
while(!in.empty()){
out.push(in.top());
in.pop();
}
}
int rtn = out.top();
out.pop();
top--;
return rtn;
}
int peek() {
if(out.empty()){ // out为空,数据在in里,搬到out中peep
while(!in.empty()){
out.push(in.top());
in.pop();
}
}
return out.top();
}
bool empty() {
return top < 0;
}
private:
stack<int> in, out; // 一个负责入队,一个负责出队
int top;
};
Leetcode 225. 用队列实现栈
与上一题不太一样,这题入队时不用将数据在两个队列间搬来搬去,因为搬了也是白搬,搬前后元素顺序不变。
pop 或 peek 时搬剩一个不搬,保存下来供返回,再 pop 掉或入队即可。
注意队列要访问队头是用 q.front()
而不是 q.top()
。
class MyStack {
private:
queue<int> in, out;
int tp;
public:
MyStack() {
tp = -1;
}
void push(int x) {
tp++;
if(!out.empty()){
while(!out.empty()){
in.push(out.front());
out.pop();
}
}
in.push(x);
}
int pop() {
int rtn, tmp=tp;
if(!out.empty()){
while(tmp--){
in.push(out.front());
out.pop();
}
rtn = out.front();
out.pop();
}
else{
while(tmp--){
out.push(in.front());
in.pop();
}
rtn = in.front();
in.pop();
}
tp --;
return rtn;
}
int top() {
int rtn, tmp=tp;
if(!out.empty()){
while(tmp--){
in.push(out.front());
out.pop();
}
rtn = out.front();
in.push(rtn);
out.pop();
}
else{
while(tmp--){
out.push(in.front());
in.pop();
}
rtn = in.front();
out.push(rtn);
in.pop();
}
return rtn;
}
bool empty() {
return in.empty() && out.empty();
}
};
Leetcode 20. 有效的括号
注意:若一开始就 push 右括号,此时栈为空,不能进行 top 或 pop 操作,所以得先判断栈是否为空再进行相应操作。
class Solution {
public:
bool isValid(string s) {
stack<char> st;
for(char c : s){
if(c == '(' || c == '[' || c == '{'){
st.push(c);
}
else{
if(st.empty()) return false; // 小细节
else if( st.top() == '(' && c ==')' ||
st.top() == '[' && c ==']' ||
st.top() == '{' && c =='}' ){
st.pop();
}
else return false;
}
}
return st.empty();
}
};
Leetcode 1047. 删除字符串中的所有相邻重复项
-
法一:栈思想
要不是放到栈专题还真想不到。用了一下 string 的 pop_back() ,真好用。
class Solution {
public:
string removeDuplicates(string s) {
string ans;
for(int i=0; i<s.length(); i++){
if(ans.empty() || s[i] != ans[ans.length()-1]){
// i==0的情况 || 中间ans为空 || 相邻两个不相同
ans.push_back(s[i]);
}
else{
ans.pop_back();
}
}
return ans;
}
};
-
法二:双指针思想
在题解里看到的,可以挑战一下。挑战失败,抄题解。。事实证明,写的方法需要考虑越多细节更容易错。。
class Solution {
public:
string removeDuplicates(string s) {
int i,j;
for( j=1; j<s.length(); j++){
i = j-1;
while(j<s.length() && s[j] == s[i]){
s[j++] = '0';
s[i] = '0';
// 啊,while(i>0 && s[i] == '0'){i--;} 跟 while(i>0 && s[i--] == '0'); 有区别。。
while(i>0 && s[i] == '0'){i--;}
}
}
for(i=0, j=0; j<s.length(); j++){
if(s[j]!='0') s[i++] = s[j];
}
s.resize(i);
return s;
}
};
// 测试样例
"ibfjcaffccadidiaidchakchchcahabhibdcejkdkfbaeeaecdjhajbkfebebfea"
"ibfjcdidiaidchakchchcahabhibdcejkdkfbecdjhajbkfebebfea"
Leetcode 150. 逆波兰表达式求值
不同于中缀表达式,逆波兰表达式不用考虑符号优先级等,无脑压栈弹栈,只需要数字栈,也不需要字符栈。
class Solution {
public:
int evalRPN(vector<string>& s) {
stack<int> a;
for(auto i : s){
if(i == "+" || i == "-" || i == "*" || i == "/"){
int y = a.top(); a.pop();
int x= a.top(); a.pop();
if(i == "*") a.push(x*y); // 其实没必要用else if
if(i == "/") a.push(x/y);
if(i == "+") a.push(x+y);
if(i == "-") a.push(x-y);
}
else a.push(stoi(i));
}
return a.top();
}
};
Leetcode 71. 简化路径
啰啰嗦嗦,简直了,怎么练 bug free,写了大致思路就开始提交然后 debug,好在leetcode有案例,ACM模式直接搞死心态。
class Solution {
public:
string simplifyPath(string path) {
int i, last;
stack<string> st;
// 消除后面斜杠,留一个
for(i=path.length()-1; i>=0 && path[i]=='/'; ){ i--;};
if(i<0) return "/";
path.resize(i+2);
path[i+1] = '/';
for(i=1, last = 0; i<path.length(); i++){
if(path[i] == '/' ){
if(path[i-1]=='/'){
last++;
continue;
}
string dir = string(path, last+1, i-last-1); // 截取目录名
if(!dir.compare("..")){
if(!st.empty()) st.pop();
}
else if(!dir.compare(".")){} // 不操作
else { // 目录名
st.push(dir);
}
last = i;
}
}
string ans="";
while(!st.empty()){
ans = "/" + st.top() + ans; // 栈存也有办法恢复路径,栈顶的目录加在字符前
st.pop();
}
return ans.length()==0? "/" : ans;
}
};
队列专题
有点陌生,一回生二回熟,刷起来。
Leetcode 239. 滑动窗口最大值
难度困难2075
class Solution {
private:
class MyQueue{ // 单调队列
private:
deque<int> q;
public:
// pop与val相等的元素
void pop(int val){
if(!q.empty() && q.front() == val){ // 注意是if
q.pop_front();
}
}
// push进val并保持队列从队头元素到队尾元素由大到小
void push(int val){
while(!q.empty() && q.back() < val){ // 注意是while
q.pop_back();
}
q.push_back(val);
}
int front(){
return q.front();
}
};
public:
vector<int> maxSlidingWindow(vector<int>& a, int k) {
MyQueue q;
vector<int> res;
int i;
for( i=0; i<k; i++){
q.push(a[i]);
}
res.push_back(q.front()); // 前k个数的最大值
for(; i<a.size(); i++){
q.pop(a[i-k]);
q.push(a[i]);
res.push_back(q.front()); // 新的滑动窗口最大值
}
return res;
}
};
Leetcode 347. 前 K 个高频元素
维护优先队列,即为堆,主要学习点是声明小根堆,还有 map 的遍历访问。
维护时有两种思路,如下所指,第二种相对更简单,但是第一种可以帮助熟悉一下对map每个元素的范围。
-
元素维护法:判断遍历到的元素出现次数是否大于当前堆顶,是进行替换。
-
堆大小维护法:若堆大小大于k,则弹出一个元素。
// 维护思路一:
class Solution {
private:
struct cmp{
bool operator()(pair<int, int> a, pair<int, int> b){
return a.second > b.second; // 小根,大于为真,为真则进行交换
}
};
public:
vector<int> topKFrequent(vector<int>& a, int k) {
unordered_map<int, int> mp;
priority_queue<pair<int, int>, vector<pair<int, int>>, cmp> q;
for(int n : a){ // 统计元素出现次数
mp[n]++;
}
for(auto it = mp.begin(); it != mp.end(); it++){ // 遍历mp
// 如果当前堆大小小于k,直接插入
if(q.size()<k) q.push(*it); // *it为pair<int const, int>
// 如果当前元素出现次数大于堆顶,删除堆顶并插入当前元素
else if(it->second > q.top().second){ // 注意it是指针,second不为函数
q.push(*it);
q.pop();
}
}
vector<int> res(k); // 声明大小为 k 的 vector
for(int i=k-1; i>=0; i--){
res[i] = q.top().first; // 不为指针
q.pop();
}
return res;
}
};
// 维护思路二:
class Solution {
private:
struct cmp{
bool operator()(pair<int, int> a, pair<int, int> b){
return a.second > b.second; // 小根,大于为真,为真则进行交换
}
};
public:
vector<int> topKFrequent(vector<int>& a, int k) {
unordered_map<int, int> mp;
priority_queue<pair<int, int>, vector<pair<int, int>>, cmp> q;
for(int n : a){ // 统计元素出现次数
mp[n]++;
}
// 逻辑不同部分
for(auto it = mp.begin(); it != mp.end(); it++){ // 遍历mp
q.push(*it); // *it为pair<int const, int>
// 如果压栈后堆元素个数大于k,弹出一个元素
if( q.size() > k ){
q.pop();
}
} // 不同部分结束
vector<int> res(k); // 声明大小为 k 的 vector
for(int i=k-1; i>=0; i--){
res[i] = q.top().first; // 不为指针
q.pop();
}
return res;
}
};