代码随想录算法训练营第十一天 | 20. 有效的括号、1047. 删除字符串中的所有相邻重复项、150. 逆波兰表达式求值

代码随想录算法训练营第十一天 | 20. 有效的括号、1047. 删除字符串中的所有相邻重复项、150. 逆波兰表达式求值

20. 有效的括号

题目链接:20. 有效的括号
注:由于栈结构的特殊性,非常适合做对称匹配类的题目。

考察对 Stack 的操作

思路:首先分析不匹配的情况有哪些,如下各图所示:

  1. 字符串里左侧的括号多余:左侧括号多余
  2. 括号数量正确,但无法匹配:类型不匹配
  3. 字符串里右侧的括号多余:右侧括号多余

对于以上三种情况,判断的方法如下:

第一种情况:已经遍历完了字符串,但是栈不为空,说明有相应的左括号没有右括号来匹配,即左括号多余,所以 return false。

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

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

若字符串遍历完之后,栈是空的,就说明全部匹配成功。

class Solution {
    public boolean isValid(String s) {
        // 剪枝:若字符串中字符总数为奇数,则一定不匹配
        if (s.length() % 2 != 0) return false;
        
        Stack<Character> stack = new Stack<>();
        for (int i = 0; i < s.length(); i++) {
            if (s.charAt(i) == '(') { // 遇到左侧括号,则向栈中压入对应的右侧括号
                stack.push(')');
            } else if (s.charAt(i) == '[') {
                stack.push(']');
            } else if (s.charAt(i) == '{') {
                stack.push('}');
            } else if (stack.empty() || s.charAt(i) != stack.peek()) {
            	// 字符串还未遍历完但栈已空(情况三) 或 遍历到的字符与栈顶元素不同(情况二)
            	// 注:须先判断是否为空,否则可能对空栈进行 peek() 操作,导致异常
                return false;
            } else {
            	// 遇到字符串中的右侧括号与栈顶元素相同,说明当前位置匹配成功,
            	// 弹出栈顶元素,开始匹配下一个字符
                stack.pop();
            }
        }
        // 字符串遍历结束,但栈不为空(情况一)
        return stack.empty();
    }
}

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

题目链接:1047. 删除字符串中的所有相邻重复项
注:为什么栈适合做类似于消除的操作?因为栈帮助记录了遍历数组当前元素时,前一个元素是什么。

考察对栈的理解和操作

思路:在删除相邻重复项的时候,其实就是要知道当前遍历的这个元素,在前一位是不是遍历过一样数值的元素,因此就用到栈来存放之前遍历过的元素。当遍历当前的各个元素时,去栈里查找是不是遍历过相同数值的相邻元素,然后再去做对应的消除操作。当遍历结束后,从栈中弹出剩余元素,因为从栈里弹出的元素是倒序的,所以再对字符串进行反转,就得到了最终的结果。

解法一: 使用 Deque 作为堆栈。
因为 ArrayDeque 会比 LinkedList 在除了删除元素这一点外会快一点。
(参考:https://stackoverflow.com/questions/6163166/why-is-arraydeque-better-than-linkedlist)

class Solution {
    public String removeDuplicates(String s) {
        ArrayDeque<Character> deque = new ArrayDeque<>();
        char ch;
        for (int i = 0; i < s.length(); i++) {
            ch = s.charAt(i);
            if (deque.isEmpty() || ch != deque.peek()) {
                deque.push(ch);
            } else {
                deque.pop();
            }
        }
        String result = "";
        while (!deque.isEmpty()) {
            result = deque.pop() + result;
        }
        return result;
    }
}

解法二:拿字符串直接作为栈,省去了栈还要转为字符串的操作。

class Solution {
    public String removeDuplicates(String s) {
        // 将 res 当做栈
        // 也可以用 StringBuilder 来修改字符串,速度更快
        // StringBuilder res = new StringBuilder();
        StringBuffer res = new StringBuffer();
        // top为 res 的长度
        int top = -1;
        for (int i = 0; i < s.length(); i++) {
            char c = s.charAt(i);
            // 当 top > 0,即栈中有字符时,当前字符如果和栈中字符相等,弹出栈顶字符,同时 top--
            if (top >= 0 && res.charAt(top) == c) {
                res.deleteCharAt(top);
                top--;
            // 否则,将该字符入栈,同时 top++
            } else {
                res.append(c);
                top++;
            }
        }
        return res.toString();
    }
}

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

此外,一种常见的错误就是栈溢出,系统输出的异常是Segmentation fault(当然不是所有的Segmentation fault 都是栈溢出导致的) 。如果使用了递归,就要想一想是不是无限递归了,那么系统调用栈就会溢出。

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

150. 逆波兰表达式求值

题目链接:150. 逆波兰表达式求值
注:逆波兰表达式相当于是二叉树中的后序遍历,即用后序遍历的方式把二叉树序列化

考察对栈的理解和操作

思路:题目已给出解题方法——遇到数字则入栈;遇到运算符则取出栈顶两个数字进行计算,并将结果压入栈中。

class Solution {
    public int evalRPN(String[] tokens) {
        Deque<Integer> stack = new LinkedList();
        for (String s : tokens) {
            if ("+".equals(s)) { // leetcode 内置 jdk 的问题,不能使用 == 判断字符串是否相等
                stack.push(stack.pop() + stack.pop);
            } else if ("-".equals(s)) { // 减号要注意顺序问题
                stack.push(-stack.pop() + stack.pop);
            } else if ("*".equals(s)) {
                stack.push(stack.pop() * stack.pop);
            } else if ("/".equals(s)) { // 除号要注意顺序问题
                int temp1 = stack.pop();
                int temp2 = stack.pop();
                stack.push(temp2 / temp1);
            } else {
                stack.push(Integer.valueOf(s));
            }
        }
        return stack.pop();
    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值