算法刷题 Leetcode 栈与队列 0117-01118

算法刷题 | 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;
    }
};

参考

代码随想录 (programmercarl.com)

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值