栈与队列

94 阅读8分钟

总结:

用栈实现队列

题目链接:232.用栈实现队列

思路

设置两个栈,分别为stack1,stack2。

入队:当采用push操作时,将元素推到stack1中去。

出队:当采用pop或者peek操作时,如果是第一次请求或此时stack2为空,将stack1中所有元素全部pop出来,推入stack2中。

此时stack2的栈顶就是所实现队列的队列头。而如果此时stack2中还有元素,则只需要pop出stack2当前栈顶元素即可。

判断是否为空,只需要判断当前两个栈stack1和stack2是否都为空即可。

实现:

class MyQueue {
private:
    stack<int> stack1;
    stack<int> stack2;

public:
    MyQueue() {

    }
    
    void push(int x) {
        stack1.push(x);
    }
    
    int pop() {
        if(stack2.empty()){
            while(!stack1.empty()){
                int tmp = stack1.top();
                stack1.pop();
                stack2.push(tmp);
            }
        }
        int value = stack2.top();
        stack2.pop();
        return value;
    }
    
    int peek() {
        if(stack2.empty()){
            while(!stack1.empty()){
                int tmp = stack1.top();
                stack1.pop();
                stack2.push(tmp);
            }
        }
        int value = stack2.top();
        return value;
    }
    
    bool empty() {
        return stack1.empty() && stack2.empty();
    }
};

用队列实现栈

题目链接: 用队列实现栈

题目: 请你仅使用两个队列实现一个后入先出(LIFO)的栈,并支持普通栈的全部四种操作(push、top、pop 和 empty)。

思路

可以仅采用一个队列来实现。

定义一个队列queue1,实现push操作,只需要将元素置入队尾即可。

实现pop操作时,可以先记录当前队列的长度length,然后不断地将元素从队首排出,再推入队列尾部,这个循环持续length - 1次。

此时队列首部的元素正是构建的栈的栈顶,将该元素从队首排出即可。

对于top操作来说,前面的循环过程于pop类似,只是当获得该元素的值后,再将该元素从队首排出,推入队列尾部。

判断实现的栈是否为空,只需要判断队列是否为空即可。

class MyStack {
private:
    queue<int> queue1;

public:
    MyStack() {

    }
    
    void push(int x) {
        queue1.push(x);
    }
    
    int pop() {
        int length = queue1.size();
        for(int i = 0;i<length-1;i++){
            int tmp = queue1.front();
            queue1.pop();
            queue1.push(tmp);
        }
        int value = queue1.front();
        queue1.pop();
        return value;
    }
    
    int top() {
        int length = queue1.size();
        for(int i = 0;i<length-1;i++){
            int tmp = queue1.front();
            queue1.pop();
            queue1.push(tmp);
        }
        int value = queue1.front();
        queue1.pop();
        queue1.push(value);
        return value;
    }
    
    bool empty() {
        return queue1.empty();
    }
};

有效的括号

题目链接:有效的括号

给定一个只包括 '(',')','{','}','[',']' 的字符串 s ,判断字符串是否有效。

有效字符串需满足:

  • 先有左括号,再有右括号
  • 左括号必须用相同类型的右括号闭合。
  • 左括号必须以正确的顺序闭合。
  • 每个右括号都有一个对应的相同类型的左括号。 文章讲解/视频讲解:programmercarl.com/0020.%E6%9C…

思路

设置一个stack,然后循环遍历字符串S,当遍历到左括号时,如’(', ‘[’, ‘{’, 将字符串push进stack中

当遍历到右括号时,判断stack栈顶的元素是否为该右括号对应的括号,如果是对应的,则将stack的栈顶pop出,

如果不是对应的,则说明字符串无效,返回false。

遍历完字符串之后,判断stack是否为空,如果不为空,说明还是无效,返回false,否则返回为true。

class Solution {
public:
    bool isValid(string s) {
        stack<char> stk;
        for(int i = 0;i<s.size();i++){
            if(s[i] == '[' || s[i] == '(' || s[i] == '{'){
                stk.push(s[i]);
            }
            else{
                if(stk.empty()) return false;
                char tmp = stk.top();
                bool success = (s[i] == ']' && tmp == '[') || (s[i] == ')' && tmp == '(') || (s[i] == '}' && tmp == '{');
                if(!success) return false;
                stk.pop();
            }
        }
        return stk.empty();
    }
};

删除字符串中的所有相邻重复项

题目链接:1047. 删除字符串中的所有相邻重复项

给出由小写字母组成的字符串 S,重复项删除操作会选择两个相邻且相同的字母,并删除它们。

在 S 上反复执行重复项删除操作,直到无法继续删除。

在完成所有重复项删除操作后返回最终的字符串。答案保证唯一。

思路

设置一个stack,这个stack可以由一个string实现。

然后循环遍历字符串s,如果当前遍历的字符和stack的栈顶相等,则将stack的栈顶pop出,并跳过这个字符。

如果当前遍历的字符和stack的栈顶不相等或者栈为空,则将字符push进这个栈。

由于我们的栈直接由string实现的,因此可以直接返回这个stack。

代码

class Solution {
public:
    string removeDuplicates(string S) {
        string result;
        for(char s : S) {
            if(result.empty() || result.back() != s) {
                result.push_back(s);
            }
            else {
                result.pop_back();
            }
        }
        return result;
    }
};

逆波兰表达式求值

题目链接:逆波兰表达式求值

给你一个字符串数组 tokens ,表示一个根据 逆波兰表示法 表示的算术表达式。

请你计算该表达式。返回一个表示表达式值的整数。

注意:

有效的算符为 '+'、'-'、'*'、 和 '/' 。 每个操作数(运算对象)都可以是一个整数或者另一个表达式。 两个整数之间的除法总是 向零截断 。 表达式中不含除零运算。 输入是一个根据逆波兰表示法表示的算术表达式。 答案及所有中间计算结果可以用 32 位 整数表示。

思路

构建一个stack,用来存储数字。

遍历tokens数组,当遇到数字时,将其存入stack,当遇到计算符号时,从stack中取出两个数字分别为num2, num1

将num1与num2做运算,再存入stack中。

最终stack中剩余的一个数字,就是逆波兰表达式的结果。

C++实现

class Solution {
public:
    int calculate(int num1, int num2, string op){
        if(op == "+"){
            return num1 + num2;
        }
        else if(op == "-"){
            return num1 - num2;
        }
        else if(op == "*"){
            return num1 * num2;
        }
        else if(op == "/"){
            return num1 / num2;
        }
        else{
            return num1 + num2;
        }
    }

    int evalRPN(vector<string>& tokens) {
        stack<int> nums;
        for(int i = 0;i<tokens.size();i++){
            if(tokens[i] != "+" && tokens[i] != "-" && tokens[i] != "*" && tokens[i] != "/"){
                nums.push(stoi(tokens[i]));
            }
            else{
                int num2 = nums.top();
                nums.pop();
                int num1 = nums.top();
                nums.pop();
                int res = calculate(num1, num2, tokens[i]);
                nums.push(res);
            }
        }
        return nums.top();
    }
};

滑动窗口最大值

题目链接:239. 滑动窗口最大值

image.png

给你一个整数数组 nums,有一个大小为 k 的滑动窗口从数组的最左侧移动到数组的最右侧。你只可以看到在滑动窗口内的 k 个数字。滑动窗口每次只向右移动一位。

返回 滑动窗口中的最大值 。

设置一个大小为k的队列queue。

在滑动窗口处于初始位置时,将初始的k个元素推入队列中: 如果队列为空或者当前元素小于队列的队尾元素,直接将nums[i]推入队列尾部; 如果当前元素大于队列的队尾元素,则循环判断,只要队列的队尾元素小于当前元素,就将当前队尾排出,直到循环判断结束,将nums[i]推入队列尾部。 此时队列的队首就是当前窗口的最大值。

滑动窗口开始移动时,开始对整数数组nums的后续元素进行遍历: 此时滑动窗口的范围为[i, i + k - 1],如果nums[i - 1]等于队首元素,则将队首排出,说明此时队首已经不在滑动窗口中了; 对于当前值nums[i],如果队列为空或nums[i]小于队列的队尾元素,直接将nums[i]推入队列尾部,如果此时队列大小大于k,将队首排出; 如果nums[i]大于队列的队尾元素,则开始循环判断,只要队列的队尾元素小于nums[i],就将当前队尾排出,直到循环判断结束,将nums[i]推入队列尾部。 同样的,每次遍历,当前队列的队首就是当前窗口的最大值。

上述方法在构建队列时,可以保证队列中的元素是单调非增的,因此队首就是当前窗口的最大值。同时,因为需要对队列的队尾做排出操作,用deque双向队列来作为队列的容器。 注:如果用vector作为容器,会超时。因为排出队首元素是o(n)复杂度的。

class Solution {
public:
    vector<int> maxSlidingWindow(vector<int>& nums, int k) {
        vector<int> results;

        deque<int> dQ;

        for(int i = 0;i<k;i++){

            if(dQ.empty() || nums[i] <= dQ.back()){
                dQ.push_back(nums[i]);
            }
            else{
                while(!dQ.empty() && dQ.back() < nums[i]) dQ.pop_back();
                dQ.push_back(nums[i]);
            }
        }

        results.push_back(dQ.front());

        for(int i = k;i<nums.size();i++){
            if(nums[i - k] == dQ.front()) dQ.pop_front();
            if(dQ.empty() || nums[i] <= dQ.back()){
                dQ.push_back(nums[i]);
                if(dQ.size() > k) dQ.pop_front();
            }
            else{
                while(!dQ.empty() && dQ.back() < nums[i]) dQ.pop_back();
                dQ.push_back(nums[i]);
            }
            results.push_back(dQ.front());
        }

        return results;
    }
};

前 K 个高频元素

题目链接:前 K 个高频元素

给你一个整数数组 nums 和一个整数 k ,请你返回其中出现频率前 k 高的元素。你可以按 任意顺序 返回答案。

文章讲解/视频讲解:programmercarl.com/0347.%E5%89…

思路

用小顶堆来实现。 定义一种node结构,属性分别为值和频率。 首先遍历数组,统计每个元素的出现频率,将代表每个元素的node存入数组frequents。 定义一个存储node类型的小顶堆,堆的判断标准是node之间的频率,频率越低越靠前。 遍历数组frequents,将元素不断地push进这个小顶堆中,如果小顶堆的大小大于k,则将小顶堆的堆顶排出。

最终小顶堆中的所有元素,构成了前K个高频元素。

代码

struct node{
    int value;
    int frequence;
};

class Solution {
public:
    vector<int> topKFrequent(vector<int>& nums, int k) {
        auto cmp = [](node a, node b){return a.frequence > b.frequence;};
        priority_queue<node, vector<node>, decltype(cmp)> Q(cmp);

        vector<node> frequents;
        unordered_map<int, int> hashMap;
        for(int i = 0;i<nums.size();i++){
            hashMap[nums[i]] += 1;
        }
        for(auto p : hashMap){
            frequents.push_back({p.first, p.second});
        }

        for(int i = 0;i<frequents.size();i++){
            Q.push(frequents[i]);
            if(Q.size() > k) Q.pop();
        }

        vector<int> results;
        while(!Q.empty()){
            results.push_back(Q.top().value);
            Q.pop();
        }
        return results;
    }
};