算法训练day11Leetcode20有效的括号1047删除字符串中所有相邻重复项150逆波兰表达式求值

本文介绍了如何使用栈数据结构解决有效括号匹配问题,通过栈来判断括号是否有效,以及如何将逆波兰表达式转换为中缀表达式并计算其值。同时提到了递归和栈溢出的概念,并提供了C++代码示例。
摘要由CSDN通过智能技术生成

今日学习的文章和视频链接

https://leetcode.cn/problems/valid-parentheses/description/
https://programmercarl.com/0020.%E6%9C%89%E6%95%88%E7%9A%84%E6%8B%AC%E5%8F%B7.html

20 有效的括号

题目描述

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

有效字符串需满足:

左括号必须用相同类型的右括号闭合。
左括号必须以正确的顺序闭合。
每个右括号都有一个对应的相同类型的左括号。
 

示例 1:

输入:s = "()"
输出:true
示例 2:

输入:s = "()[]{}"
输出:true
示例 3:

输入:s = "(]"
输出:false
 

提示:

1 <= s.length <= 104
s 仅由括号 '()[]{}' 组成

我遇到的问题

判断右括号后忘记pop

题目分析

括号匹配是使用栈解决的经典问题。

如果还记得编译原理的话,编译器在 词法分析的过程中处理括号、花括号等这个符号的逻辑,也是使用了栈这种数据结构。

由于栈结构的特殊性,非常适合做对称匹配类的题目。

首先要弄清楚,字符串里的括号不匹配有几种情况。

先来分析一下 这里有三种不匹配的情况,

  1. 第一种情况,字符串里左方向的括号多余了 ,所以不匹配。
  2. 括号没有多余,但是括号的类型没有匹配上
  3. 字符串里右方向的括号多余了,所以不匹配
    第一种情况:已经遍历完了字符串,但是栈不为空,说明有相应的左括号没有右括号来匹配,所以return false

第二种情况:遍历字符串匹配的过程中,发现栈里没有要匹配的字符。所以return false

第三种情况:遍历字符串匹配的过程中,栈已经为空了,没有匹配的字符了,说明右括号没有找到对应的左括号return false

那么什么时候说明左括号和右括号全都匹配了呢,就是字符串遍历完之后,栈是空的,就说明全都匹配了。

但还有一些技巧,在匹配左括号的时候,右括号先入栈,就只需要比较当前元素和栈顶相不相等就可以了,比左括号先入栈代码实现要简单的多了!

我的acm模式代码

#include <iostream>
#include <stack>

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

int main() {
    std::string s = "(())[]{}";
    Solution sol;
    if(sol.isValid(s)) {
        std::cout << "valid string" <<std::endl;
    }
    else {
        std::cout << "unvalid string" << std::endl;
    }
}

这段C++代码的目的是检查一个只包含圆括号、方括号和花括号的字符串是否有效。它用于判断输入的字符串是否含有有效的 {}()[] 配对和顺序。

时间复杂度分析:

  1. 线性时间复杂度 (O(n)): isValid 函数遍历输入字符串 s 的每个字符一次。因此,时间复杂度与字符串的长度成正比,导致线性时间复杂度为 O(n),其中 n 是字符串的长度。

空间复杂度分析:

  1. 线性空间复杂度 (O(n)): 空间复杂度主要由栈 st 的使用决定。在最坏的情况下(例如,一个开括号的字符串),栈可能会存储几乎每个字符,直到开始弹出为止。因此,空间复杂度也是线性的,O(n),与输入字符串的长度成正比。

总的来说,考虑到线性的时间和空间复杂度,这个函数对于合理长度的字符串来说是高效的。

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

题目分析

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

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

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

 

示例:

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

提示:

1 <= S.length <= 20000
S 仅由小写英文字母组成。

我看到题目后的想法

和上1题类似,用栈来存储,进行类似消消乐,最后逆序保存栈顶元素作为输出

题目分析

我们在删除相邻重复项的时候,其实就是要知道当前遍历的这个元素,我们在前一位是不是遍历过一样数值的元素,那么如何记录前面遍历过的元素呢?

所以就是用栈来存放,那么栈的目的,就是存放遍历过的元素,当遍历当前的这个元素的时候,去栈里看一下我们是不是遍历过相同数值的相邻元素。

从栈中弹出剩余元素,此时是字符串ac,因为从栈里弹出的元素是倒序的,所以再对字符串进行反转一下,就得到了最终的结果。

这道题目就像是我们玩过的游戏对对碰,如果相同的元素挨在一起就要消除。

可能我们在玩游戏的时候感觉理所当然应该消除,但程序又怎么知道该如何消除呢,特别是消除之后又有新的元素可能挨在一起。

此时游戏的后端逻辑就可以用一个栈来实现(我没有实际考察对对碰或者爱消除游戏的代码实现,仅从原理上进行推断)。

游戏开发可能使用栈结构,编程语言的一些功能实现也会使用栈结构,实现函数递归调用就需要栈,但不是每种编程语言都支持递归,例如:

递归的实现就是:每一次递归调用都会把函数的局部变量、参数值和返回地址等压入调用栈中,然后递归返回的时候,从栈顶弹出上一次递归的各项参数,所以这就是递归为什么可以返回上一层位置的原因。

相信大家应该遇到过一种错误就是栈溢出,系统输出的异常是Segmentation fault(当然不是所有的Segmentation fault 都是栈溢出导致的) ,如果你使用了递归,就要想一想是不是无限递归了,那么系统调用栈就会溢出。

而且在企业项目开发中,尽量不要使用递归!在项目比较大的时候,由于参数多,全局变量等等,使用递归很容易判断不充分return的条件,非常容易无限递归(或者递归层级过深),造成栈溢出错误(这种问题还不好排查!)

我的acm模式代码

#include <iostream>
#include <stack>

class Solution {
public:
    std::string removeDuplicates(std::string s) {
        std::stack<char> st;
        for (int i = 0; i < s.size(); i++) {
            if (st.empty() || st.top() != s[i]) {
                st.push(s[i]);
            }
            else {
                st.pop();
            }
        }
        std::string res;
        res.resize(st.size());
        int n = st.size();
        for (int i = st.size() - 1; i >= 0; i--) {
            res[i] = st.top();
            st.pop();
        }
        return res;
    }
};

int main() {
    std::string s = "abbaca";
    Solution sol;
    std::string res = sol.removeDuplicates(s);
    std::cout << res <<std::endl;
}

我遇到的困难

自己写的时候没有进行res.resize.无法给res进行赋值

问题出在尝试向 std::string res 赋值的方式上。当使用 res[i] = st.top(); 这行代码时,假设 res 是一个空字符串,实际上在尝试访问并修改一个不存在的字符位置。在 C++ 中,这种操作是不合法的,因为在访问未被初始化或分配的内存。

解决这个问题有几种方法:

  1. 使用 push_back+= 在遍历栈时,可以使用 push_back+= 来向 res 中添加字符。例如,res.push_back(st.top());

  2. 预先分配字符串大小: 另一种方法是,在填充 res 之前,先设置它的大小。可以在将字符弹出栈之前,使用 std::stringresize 方法来设置字符串的大小。

修改后的代码示例如下:

class Solution {
public:
    std::string removeDuplicates(std::string s) {
        std::stack<char> st;
        for (char ch : s) {
            if (st.empty() || st.top() != ch) {
                st.push(ch);
            }
            else {
                st.pop();
            }
        }
        std::string res;
        res.resize(st.size()); // 设置字符串大小
        for (int i = st.size() - 1; i >= 0; i--) {
            res[i] = st.top();
            st.pop();
        }
        return res;
    }
};

或者,使用 push_back

class Solution {
public:
    std::string removeDuplicates(std::string s) {
        std::stack<char> st;
        for (char ch : s) {
            if (st.empty() || st.top() != ch) {
                st.push(ch);
            }
            else {
                st.pop();
            }
        }
        std::string res;
        while (!st.empty()) {
            res = st.top() + res; // 在字符串前面添加字符
            st.pop();
        }
        return res;
    }
};

这两种方法都能有效地解决无法给 res 赋值的问题。

逆波兰表达式求值

题目分析

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

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

注意:

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

示例 1:

输入:tokens = ["2","1","+","3","*"]
输出:9
解释:该算式转化为常见的中缀算术表达式为:((2 + 1) * 3) = 9
示例 2:

输入:tokens = ["4","13","5","/","+"]
输出:6
解释:该算式转化为常见的中缀算术表达式为:(4 + (13 / 5)) = 6
示例 3:

输入:tokens = ["10","6","9","3","+","-11","*","/","*","17","+","5","+"]
输出:22
解释:该算式转化为常见的中缀算术表达式为:
  ((10 * (6 / ((9 + 3) * -11))) + 17) + 5
= ((10 * (6 / (12 * -11))) + 17) + 5
= ((10 * (6 / -132)) + 17) + 5
= ((10 * 0) + 17) + 5
= (0 + 17) + 5
= 17 + 5
= 22
 

提示:

1 <= tokens.length <= 104
tokens[i] 是一个算符("+"、"-"、"*" 或 "/"),或是在范围 [-200, 200] 内的一个整数
 

逆波兰表达式:

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

平常使用的算式则是一种中缀表达式,如 ( 1 + 2 ) * ( 3 + 4 ) 。
该算式的逆波兰表达式写法为 ( ( 1 2 + ) ( 3 4 + ) * ) 。
逆波兰表达式主要有以下两个优点:

去掉括号后表达式无歧义,上式即便写成 1 2 + 3 4 + * 也可以依据次序计算出正确结果。
适合用栈操作运算:遇到数字则入栈;遇到算符则取出栈顶两个数字进行计算,并将结果压入栈中

我的想法

定义两个整数变量,用来存储操作数
首先遍历将数字入栈,遇到第一个符号时,将前两个数字元素出栈进行符号运算,结果再入栈,

题目分析

其实逆波兰表达式相当于是二叉树中的后序遍历。 大家可以把运算符作为中间节点,按照后序遍历的规则画出一个二叉树。

但我们没有必要从二叉树的角度去解决这个问题,只要知道逆波兰表达式是用后序遍历的方式把二叉树序列化了,就可以了。

在进一步看,本题中每一个子表达式要得出一个结果,然后拿这个结果再进行运算,那么这岂不就是一个相邻字符串消除的过程,和1047.删除字符串中的所有相邻重复项 (opens new window)中的对对碰游戏是不是就非常像了。

我们习惯看到的表达式都是中缀表达式,因为符合我们的习惯,但是中缀表达式对于计算机来说就不是很友好了。

例如:4 + 13 / 5,这就是中缀表达式,计算机从左到右去扫描的话,扫到13,还要判断13后面是什么运算符,还要比较一下优先级,然后13还和后面的5做运算,做完运算之后,还要向前回退到 4 的位置,继续做加法,你说麻不麻烦!

那么将中缀表达式,转化为后缀表达式之后:[“4”, “13”, “5”, “/”, “+”] ,就不一样了,计算机可以利用栈来顺序处理,不需要考虑优先级了。也不用回退了, 所以后缀表达式对计算机来说是非常友好的。

可以说本题不仅仅是一道好题,也展现出计算机的思考方式。

在1970年代和1980年代,惠普在其所有台式和手持式计算器中都使用了RPN(后缀表达式),直到2020年代仍在某些模型中使用了RPN。

我的acm代码

#include <iostream>
#include <vector>
#include <stack>
#include <string>
class Solution {
public:
    int evalRPN(std::vector<std::string>& tokens) {
        int a,res;
        if (tokens.size() == 1) {
            res = std::stoi(tokens[0]);
            return res;
        }
        std::stack<std::string> st;
        for (std::string s:tokens) {
            if (s != "+" && s != "-" && s != "*" && s != "/") {
                st.push(s);
            }
            else {
                res = std::stoi(st.top());
                st.pop();
                a = std::stoi(st.top());
                st.pop();
                if (s == "+") {
                    res = a + res;
                }
                else if (s == "-") {
                    res = a - res;
                }
                else if (s == "*") {
                    res = a * res;
                }
                else if (s == "/") {
                    res = a / res;
                }
                st.push(std::to_string(res));
            }
        }
        return res;
    }
};

int main () {
    // std::vector<std::string> tokens = {"2","1","+","3","*"};
    std::vector<std::string> tokens = {"10","6","9","3","+","-11","*","/","*","17","+","5","+"};
    Solution sol;
    int res = sol.evalRPN(tokens);
    std::cout << res << std::endl;
}

我遇到的困难

没有考虑到只有一个元素的情况,Leetcode上有一个实例没有通过,加上以下代码

if (tokens.size() == 1) {
            res = std::stoi(tokens[0]);
            return res;
        }
  • 19
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值