代码随想录DAY11 - 栈和队列 - 08/10

目录

有效的括号

题干

思路

代码

方法一:左括号先入栈

方法二:右括号先入栈

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

题干

思路

代码

方法一:使用栈空间

方法二:使用字符串当栈

逆波兰表达式求值

逆波兰表达式

题干

思路

代码

滑动窗口最大值

题干

思路

代码


有效的括号

题干

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

有效字符串需满足:

  • 左括号必须用相同类型的右括号闭合。

  • 左括号必须以正确的顺序闭合。

  • 注意空字符串可被认为是有效字符串。

链接:. - 力扣(LeetCode)

思路

括号匹配问题是栈的经典应用。

方法一 左括号先入栈:遇到左括号则压栈,遇到右括号立即查询栈中是否有匹配的左括号有则弹出左括号。如果最后栈空和字符串都遍历完毕则说明匹配成功;如果栈空而字符串还没遍历完说明左括号缺失;如果栈非空而字符串遍历完毕说明右括号缺失;如果左右括号不匹配也是直接return false。

方法二 右括号先入栈:当遍历到左括号时,让和左括号匹配的右括号压栈,这样当遍历到右括号时,只需查询右括号和栈顶元素是否相等即可。如果不相等就说明和之前的左括号并不匹配。而左括号或右括号缺失的情况和方法一相同。

代码

方法一:左括号先入栈
class Solution {
public:
    bool isValid(string s) {
        stack<char> brackets;
        for (char c : s) {
            // 遇到左括号压栈
            if (c == '(' || c == '[' || c == '{'){
                brackets.push(c);
            } else{
                // 遇到右括号看栈是否为空
                // 若栈空说明左括号缺失
                if (brackets.empty()){
                    return false;
                } else{
                    // 若栈非空查看是否匹配
                    if ((c == ')' && brackets.top() == '(') ||
                        (c == ']' && brackets.top() == '[') ||
                        (c == '}' && brackets.top() == '{') ){
                        // 若匹配则弹栈
                        brackets.pop();
                    } else{
                        // 不匹配直接return
                        return false;
                    }
                }
            }
        }
        // 最后栈空说明全部匹配完成
        if (brackets.empty()){
            return true;
        } else{
            // 栈非空说明右括号缺失
            return false;
        }
    }
};
方法二:右括号先入栈
public:
    bool isValid(string s) {
        // 要使括号匹配,字符串的长度肯定是2的倍数,即偶数,如果是奇数肯定不匹配
        if (s.size()%2 != 0){
            return false;
        }
        stack<char> brackets;
        for (char c : s) {
            if (c == '('){
                brackets.push(')');
            } else if (c == '['){
                brackets.push(']');
            } else if ( c == '{'){
                brackets.push('}');
            } else{
                // 当栈为空 或者 栈顶元素和当前右括号不相等,说明不匹配
                if (brackets.empty() || c!=brackets.top()){
                    return false;
                }
                // 当栈顶元素和当前右括号相等,说明匹配,弹栈
                brackets.pop();
            }
        }
        return brackets.empty();
    }
};

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

题干

题目:给出由小写字母组成的字符串 S,重复项删除操作会选择两个相邻且相同的字母,并删除它们。在 S 上反复执行重复项删除操作,直到无法继续删除。在完成所有重复项删除操作后返回最终的字符串。答案保证唯一。

示例:

  • 输入:"abbaca"

  • 输出:"ca"

  • 解释:例如,在 "abbaca" 中,我们可以删除 "bb" 由于两字母相邻且相同,这是此时唯一可以执行删除操作的重复项。之后我们得到字符串 "aaca",其中又只有 "aa" 可以执行重复项删除操作,所以最后的字符串为 "ca"。

链接:. - 力扣(LeetCode)

思路

方法一使用栈空间:遍历字符串,当遇到和之前不同的字母时压栈,当遇到相同字母时弹栈,这样最后栈中剩余的元素就是删除后的结果。但由于栈是先进后出,所以将栈中剩余的元素存储到结果字符串时元素是颠倒的,最后只需反转字符串即可。时间复杂度O(n),空间复杂度O(n)。

方法二用新的字符串当作栈:思路和方法一相同,只不过使用一个新的空字符串来作为栈使用,就不需要后续将栈转为字符串了。时间复杂度O(n),空间复杂度O(1)。

代码

方法一:使用栈空间
class Solution {
public:
    string removeDuplicates(string s) {
        stack<char> characters;
        for (char c : s) {
            if (characters.empty() || characters.top() != c){
                characters.push(c);
            } else{
                // 栈顶元素和当前字母相同
                characters.pop();
            }
        }
        if (characters.empty()){
            return "";
        }
        s.clear();
        // 把栈中剩余的元素都弹出到字符串中
        while (!characters.empty()){
            s.push_back(characters.top());
            characters.pop();
        }
        // 最后反转字符串
        reverse(s.begin(),s.end());
        return s;
    }
};
方法二:使用字符串当栈
class Solution {
public:
    string removeDuplicates(string s) {
        string characters = "";
        for (char c : s) {
            if (characters.empty() || characters.back() != c){
                // 当元素不同,存入字符串
                characters.push_back(c);
            } else{
                // 碰到相同元素,弹出字符串最后一个元素。
                characters.pop_back();
            }
        }
        return characters;
    }
};

逆波兰表达式求值

逆波兰表达式

逆波兰表达式是一种后缀表达式,所谓后缀就是指算符写在后面。

  • 平常使用的算式是一种中缀表达式,如 ( 1 + 2 ) * ( 3 + 4 )

  • 该算式的逆波兰表达式写法为 ( ( 1 2 + ) ( 3 4 + ) * )

逆波兰表达式主要有以下两个优点:

  • 去掉括号后表达式无歧义,上式即便写成 1 2 + 3 4 + * 也可以依据次序计算出正确结果。

  • 适合用栈操作运算:遇到数字则入栈;遇到算符则取出栈顶两个数字进行计算,并将结果压入栈中

题干

题目:给你一个字符串数组 tokens ,存储着根据 逆波兰表示法 表示的算术表达式。根据 逆波兰表示法,求该表达式的值。有效的运算符包括 + , - , * , / 。每个运算对象可以是整数,也可以是另一个逆波兰表达式。

说明:整数除法只保留整数部分。 给定逆波兰表达式总是有效的。换句话说,表达式总会得出有效数值且不存在除数为 0 的情况。

链接:. - 力扣(LeetCode)

思路

遍历字符串,遇到数字,则将数字字符串转化为整数压入栈,遇到运算符则取出栈顶两个数字进行计算,并将中间结果压入栈中,遍历完字符串,栈中最后只剩下一个元素即运算结果。

代码

class Solution {
public:
    // 定义运算
    int operation(string s, int a, int b){
        if (s == "+"){
            return a+b;
        } else if (s == "-"){
            return a-b;
        } else if (s == "*"){
            return a*b;
        } else{
            return a/b;
        }
    }
    // 求逆波兰表达式的值
    int evalRPN(vector<string>& tokens) {
        stack<int> num; // 存储操作数和中间运算的结果
        for (int i = 0; i < tokens.size(); ++i) {
            string s = tokens[i];
            if (s == "+" || s == "-" || s == "*" || s == "/"){
                // 弹出两个操作数,b是第二个操作数
                int b = num.top();
                num.pop();
                // a 是第一个操作数
                int a = num.top();
                num.pop();
                // 将中间的运算结果压回栈中
                num.push(operation(s,a,b));
            }else{
                // 碰到操作数,将字符串转化为整数压入栈中
                num.push(stoi(s));
            }
        }
        int result = num.top(); // 最后栈中剩余的一个元素就是运算结果
        return result;
    }
};

滑动窗口最大值

题干

题目:给定一个数组 nums,有一个大小为 k 的滑动窗口从数组的最左侧移动到数组的最右侧。你只可以看到在滑动窗口内的 k 个数字。滑动窗口每次只向右移动一位。将每次滑动窗口中的最大值存储在数组中返回。

进阶:你能在线性时间复杂度内解决此题吗?

链接:. - 力扣(LeetCode)

思路

方法一暴力解法:使用两个for循环,一个for循环遍历数组滑动窗口,一个for循环遍历窗口找到最大值,时间复杂度O(n*k),其中n为数组长度,k为窗口长度,超出时间限制,就不放代码了。

方法二使用队列:因为每次窗口向右移动一位,则窗口最左边的元素都会被弹出,符合队列先进先出的原则,因此可以用队列存储窗口中的值。同时为了找到每次窗口的最大值,可以让元素从大到小排列在队列中,最大值即队头元素,但是我们滑动窗口时还需要把窗口要移除的元素弹出,那如何在排过序的队列中找到这个要移除的元素呢?此时就出现了矛盾,因此我们需要思考以下这个问题。

队列是否有必要维护窗口所有的元素?

其实队列没有必要维护窗口里的所有元素,我们要找的是窗口最大值,那么只需要维护有可能成为窗口最大值的元素,并且保证队列里的元素是降序排列即可。因此在遍历数组时,假如遍历到的值为value,value如果比队尾元素小就直接插入队列;而如果比队尾元素大,我们就把队列里所有比 value 值小的元素弹出,因为这些比 value 小的元素都不可能成为最大值。

代码

class Solution {
private:
    // 自定义队列
    class MyQueue{
    public:
        deque<int> que;
        void pop(int value) {
            // 当要弹出的元素为队头元素时,才需要弹出
            // 如果要弹出的元素不是队头元素,说明这个元素比队头元素要小,在之前push队头元素时就已经被弹出了
            if (!que.empty() && value == que.front()){
                que.pop_front();
            }
        }
        void push(int value) {
            while (!que.empty() && value > que.back()){
                // 从队尾弹出所有比 value 小的元素,让队列里只剩下比 value 大的
                // 保持队列的降序排列
                que.pop_back();
            }
            que.push_back(value);
        }
        int front() {
            return que.front();
        }
    };
public:
    vector<int> maxSlidingWindow(vector<int>& nums, int k) {
        // 滑动窗口大小为 1,则返回原数组
        if (k == 1){
            return nums;
        }
        MyQueue window; // 队列存储窗口可能的最大值
        vector<int> result;// 存储每个窗口的最大值
     
        // 先存储前 k 个元素
        for (int i = 0; i < k; ++i) {
            window.push(nums[i]);
        }
        result.push_back(window.front()); // 存储第一个窗口的最大值
        // 遍历接下来的窗口
        for (int i = k; i < nums.size(); ++i) {
            // i 指向的是每次右移窗口后要插入的新元素的位置
            // 要准备弹出 nums[i-k] 这个元素,插入 nums[i]
            window.pop(nums[i-k]);
            window.push(nums[i]);
            // 储存当前窗口的最大值
            result.push_back(window.front());
        }
        return result;
    }
};

 

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值