LeetCode(1)-栈:符号匹配类问题

本文精选LeetCode中与栈相关的经典题目,包括括号验证、字符串比较、棒球比赛计分、添加有效括号等,通过实例解析栈的基本原理及应用场景。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

 


文章目录

 

本文是LeetCode刷题总结第一篇之栈的典型应用:符号匹配。栈最典型的特征就是它的后进先出的特性,这个特性在许多方面被广泛应用,只要是看到题目中关于“括号匹配”、“对称结构判断”等字眼,就应该首先想到栈这种数据结构。

一、LeetCode 20.有效的括号

给定一个只包括 ‘(’,’)’,’{’,’}’,’[’,’]’ 的字符串,判断字符串是否有效。
有效字符串需满足:
左括号必须用相同类型的右括号闭合。
左括号必须以正确的顺序闭合。
注意空字符串可被认为是有效字符串。
示例 1:
输入: “()”
输出: true
示例 2:
输入: “()[]{}”
输出: true
示例 3:
输入: “(]”
输出: false
示例 4:
输入: “([)]”
输出: false
示例 5:
输入: “{[]}”
输出: true
来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/valid-parentheses
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。

这道题是非常经典的栈的应用,它的思路就是利用栈的特性:当遇到{、【、(时,将其压入栈顶;当遇到}、】、)时将栈顶弹出与其匹配,若匹配不上则说明其并不是一个有效的括号结构。

class Solution {
    public boolean isValid(String s) {
        //空字符串判断为true
        if (s.trim().equals("")) return true;
        //奇数个元素则为false
        if (s.length() % 2 == 1) return false;
        Stack<Character> stack = new Stack<>();
        for (char c : s.toCharArray()) {
            switch (c) {
                case '{':
                    stack.push('}');
                    break;

                case '(':
                    stack.push(')');
                    break;

                case '[':
                    stack.push(']');
                    break;
                    
                default:
                    //} ) ]
                    //如果在弹栈过程中发现栈空了,说明左符号是少于右符号的,返回false
                    //如果在弹栈过程中发现不匹配,返回false
                     if (stack.isEmpty() || c != stack.pop()) {
                        return false;
                    }
            }
        }
        //如果弹栈过程完成后仍然存在元素,则说明右符号是多于左符号的,返回false
        return stack.isEmpty();
    }
}

二、LeetCode 844. 比较含退格的字符串

给定 S 和 T 两个字符串,当它们分别被输入到空白的文本编辑器后,判断二者是否相等,并返回结果。 # 代表退格字符。
示例 1:
输入:S = “ab#c”, T = “ad#c”
输出:true
解释:S 和 T 都会变成 “ac”。
示例 2:
输入:S = “ab##”, T = “c#d#”
输出:true
解释:S 和 T 都会变成 “”。
示例 3:
输入:S = “a##c”, T = “#a#c”
输出:true
解释:S 和 T 都会变成 “c”。
示例 4:
输入:S = “a#c”, T = “b”
输出:false
解释:S 会变成 “c”,但 T 仍然是 “b”。
提示:
1 <= S.length <= 200
1 <= T.length <= 200
S 和 T 只含有小写字母以及字符 ‘#’。
来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/backspace-string-compare
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。

这道题用栈实现的话是很容易的,只需要让字母入栈,当遇到#时弹栈(栈非空的话),最后再逐一弹栈比较两个栈中的元素是否相等即可:

class Solution {
     public boolean backspaceCompare(String S, String T) {
            Stack<Character> stack1 = new Stack<>();
       	    Stack<Character> stack2 = new Stack<>();
        //S入栈
       for (int i = 0; i < S.length(); i++) {
            char c = S.charAt(i);
            if (c == '#' && !stack1.isEmpty()) {
                //弹栈
                stack1.pop();
            } else if (c != '#') {
                stack1.push(c);
            }
        }
        //T入栈
        for (int i = 0; i < T.length(); i++) {
            char c = T.charAt(i);
            if (c == '#' && !stack2.isEmpty()) {
                //弹栈
                stack2.pop();
            } else if (c != '#') {
                stack2.push(c);
            }
        }
        //比较
        if (stack1.size() != stack2.size()) return false;
        while (!stack1.isEmpty()) {
            if (stack1.pop() != stack2.pop()) return false;
        }
        return true;
    }


}

三、LeetCode 682. 棒球比赛

你现在是棒球比赛记录员。
给定一个字符串列表,每个字符串可以是以下四种类型之一:
1.整数(一轮的得分):直接表示您在本轮中获得的积分数。
2. “+”(一轮的得分):表示本轮获得的得分是前两轮有效 回合得分的总和。
3. “D”(一轮的得分):表示本轮获得的得分是前一轮有效 回合得分的两倍。
4. “C”(一个操作,这不是一个回合的分数):表示您获得的最后一个有效 回合的分数是无效的,应该被移除。

每一轮的操作都是永久性的,可能会对前一轮和后一轮产生影响。
你需要返回你在所有回合中得分的总和。

示例 1:

输入: [“5”,“2”,“C”,“D”,"+"]
输出: 30
解释:
第1轮:你可以得到5分。总和是:5。
第2轮:你可以得到2分。总和是:7。
操作1:第2轮的数据无效。总和是:5。
第3轮:你可以得到10分(第2轮的数据已被删除)。总数是:15。
第4轮:你可以得到5 + 10 = 15分。总数是:30。
示例 2:

输入: [“5”,"-2",“4”,“C”,“D”,“9”,"+","+"]
输出: 27
解释:
第1轮:你可以得到5分。总和是:5。
第2轮:你可以得到-2分。总数是:3。
第3轮:你可以得到4分。总和是:7。
操作1:第3轮的数据无效。总数是:3。
第4轮:你可以得到-4分(第三轮的数据已被删除)。总和是:-1。
第5轮:你可以得到9分。总数是:8。
第6轮:你可以得到-4 + 9 = 5分。总数是13。
第7轮:你可以得到9 + 5 = 14分。总数是27。
注意:

输入列表的大小将介于1和1000之间。
列表中的每个整数都将介于-30000和30000之间。

来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/baseball-game
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。

这道题只需要按题目要求操作即可,需要注意的点是栈中需要留下所有有效的分数,之后再求和栈的元素就可以了:

class Solution {
  public int calPoints(String[] ops) {
        Stack<Integer> stack = new Stack<>();
        int totalCount = 0;
        for (int i = 0; i < ops.length; i++) {
            String v = ops[i];
            switch (v) {
                case "C":
                    //上一轮有效分数失效,出栈
                    stack.pop();
                    break;

                case "D":
                    //本轮获取的分数是上一轮有效分数的两倍
                    int curPoint1 = 2 * stack.peek();
                    stack.push(curPoint1);
                    break;

                case "+":
                    //本轮获取分数是上两轮分数之和
                    //上一轮分数
                    int prePoint = stack.pop();
                    //上第二轮分数
                    int pprePoint = stack.peek();
                    //本轮分数
                    int curPoint2 = prePoint + pprePoint;
                    //先放回上一轮分数
                    stack.push(prePoint);
                    //放入本轮分数
                    stack.push(curPoint2);
                    break;

                default:
                    //是直接分数
                    int point = Integer.parseInt(v);
                    stack.push(point);
                    break;

            }
        }

        while (!stack.isEmpty()) {
            totalCount += stack.pop();
        }
        return totalCount;
    }
}

四、LeetCode 921. 使括号有效的最少添加

给定一个由 ‘(’ 和 ‘)’ 括号组成的字符串 S,我们需要添加最少的括号( ‘(’ 或是 ‘)’,可以在任何位置),以使得到的括号字符串有效。

从形式上讲,只有满足下面几点之一,括号字符串才是有效的:

它是一个空字符串,或者
它可以被写成 AB (A 与 B 连接), 其中 A 和 B 都是有效字符串,或者
它可以被写作 (A),其中 A 是有效字符串。
给定一个括号字符串,返回为使结果字符串有效而必须添加的最少括号数。

示例 1:

输入:"())"
输出:1
示例 2:

输入:"((("
输出:3
示例 3:

输入:"()"
输出:0
示例 4:

输入:"()))(("
输出:4

提示:

S.length <= 1000
S 只包含 ‘(’ 和 ‘)’ 字符。

来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/minimum-add-to-make-parentheses-valid
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。

这道题的思路是当遇到(时,将其入栈,当遇到)时需要匹配栈头,若栈为空或者栈头是(则将栈头出栈,否则将)入栈,最后只需要计算栈中剩余元素的多少即可知道需要多少个括号来与之匹配了:

class Solution {
    public int minAddToMakeValid(String S) {
        Stack<Character> stack = new Stack<>();
        int n = S.length();
        for (int i = 0; i < n; i++) {
            char c = S.charAt(i);
            if (c == '(') {
                stack.push('(');
            } else {
                if(!stack.isEmpty()) {
                    char peek = stack.peek();
                    if (peek == '(') {
                        stack.pop();
                    } else {
                         stack.push(')');
                    }
                } else {
                    stack.push(')');
                }
              
            }
        }
        int res = 0;
        if (!stack.isEmpty()) {
            res = stack.size();
        }
        return res;
    }
}

五、LeetCode 1021. 删除最外层的括号

有效括号字符串为空 ("")、"(" + A + “)” 或 A + B,其中 A 和 B 都是有效的括号字符串,+ 代表字符串的连接。例如,"","()","(())()" 和 “(()(()))” 都是有效的括号字符串。

如果有效字符串 S 非空,且不存在将其拆分为 S = A+B 的方法,我们称其为原语(primitive),其中 A 和 B 都是非空有效括号字符串。

给出一个非空有效字符串 S,考虑将其进行原语化分解,使得:S = P_1 + P_2 + … + P_k,其中 P_i 是有效括号字符串原语。

对 S 进行原语化分解,删除分解中每个原语字符串的最外层括号,返回 S 。

示例 1:

输入:"(()())(())"
输出:"()()()"
解释:
输入字符串为 “(()())(())”,原语化分解得到 “(()())” + “(())”,
删除每个部分中的最外层括号后得到 “()()” + “()” = “()()()”。
示例 2:

输入:"(()())(())(()(()))"
输出:"()()()()(())"
解释:
输入字符串为 “(()())(())(()(()))”,原语化分解得到 “(()())” + “(())” + “(()(()))”,
删除每隔部分中的最外层括号后得到 “()()” + “()” + “()(())” = “()()()()(())”。
示例 3:

输入:"()()"
输出:""
解释:
输入字符串为 “()()”,原语化分解得到 “()” + “()”,
删除每个部分中的最外层括号后得到 “” + “” = “”。

提示:

S.length <= 10000
S[i] 为 “(” 或 “)”
S 是一个有效括号字符串

来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/remove-outermost-parentheses
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。

这道题的思路是创建一个StringBuilder res用以存储结果,开始时需要设置一个是否为左外括号的标志flag = true,遍历字符串,当flag为true时,不将这个字符添加到res中,因为它是左外括号,然后将flag置为false,然后依次将之后的元素入栈和添加到res的末尾(注意,当碰到)时需要弹栈),当遇到某个)且栈为空时,说明这是无法被匹配的右外括号(因为一开始左外括号不添加到栈中),此时不将)添加到栈中和res中,随后将flag置为true,下一个字符即为下一轮的左外括号。

class Solution {
   public String removeOuterParentheses(String S) {
        //是否为左外括号
        boolean flag = true;
        int n = S.length();
        StringBuffer res = new StringBuffer();
        Stack<Character> stack = new Stack<>();
        for (int i = 0; i < n; i++) {
            char c = S.charAt(i);
            if (c == '(') {
                //判断是否为左外括号
                if (flag) {
                    //是左外括号,不将其添加到res中,将标志置为false
                    flag = false;
                } else {
                    //不是左外括号,将其添加到res和压入stack中
                    res.append(c);
                    stack.push(c);
                }
            } else if (c == ')') {
                //和栈顶匹配,当栈为空时,即无法匹配时说明这是右外括号,不添加到res中
                if (stack.isEmpty()) {
                    //出现了无法被匹配的右括号,此时将flag置为true。下一个迭代开始即为左外括号
                    flag = true;
                } else {
                    stack.pop();
                    res.append(c);
                }

            }

        }
        return res.toString();
        }
}

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

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

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

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

示例:

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

来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/remove-all-adjacent-duplicates-in-string
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。

这道题也是典型的栈应用题目,感觉做这题的时候有一种玩消消乐的感觉:

class Solution {
    public String removeDuplicates(String S) {
        StringBuffer res = new StringBuffer();
        Stack<Character> stack = new Stack<>();
        int n = S.length();
        for (int i = 0; i < n; i++) {
            char c = S.charAt(i);
            if (stack.isEmpty()) {
                stack.push(c);
            } else {
                Character peek = stack.peek();
                if (peek.equals(c)) {
                    stack.pop();
                } else {
                    stack.push(c);
                } 
            }
        }
            while (!stack.isEmpty()) {
                res.append(stack.pop());
            }
        return res.reverse().toString();
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

BoringRong

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值