题目描述

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

  • 左括号必须用相同类型的右括号闭合。
  • 左括号必须以正确的顺序闭合。

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

示例1:

输入:"()"
输出:true

示例2:

输入:"()[ ] { } "
输出:true

示例3:

输入:"( ] "
输出:false

示例4:

输入:"( [ )] "
输出:false

示例5:

输入:" { [ ] } "
输出:true

简化版本

让我们看一下该问题的简化版本,在简化后的问题中,只含一种类型的括号。这么一来,我们将会遇到的表达式是

( ( ( ( ( ( ) ) ) ) ) )   - VALID
( ) ( ) ( ) ( )               -VALID
( ( ( ( ( ( ( ( )          -INVALID
( ( ( ) ( ( ) ) ) )       - VALID

我们试着用一个简单的算法来解决这一问题。

  1. 我们从表达式的左侧开始,每次只处理一个括号。

  2. 假设我们遇到一个开括号即,表达式是否无效取决于这个表达式的其它部分是否有相匹配的闭括号即 。此时,我们只是增加计数器的值保持跟踪现在为止开括号的数目。left += 1 。

  3. 如果我们遇到一个闭括号,这可能意味着这样两种情况:

    ·  此闭括号没有与之对应的开括号,在这种情况下,我们的表达式无效。当 left == 0,也就是没有未配对的左括号可用时就是这种情况。

    ·  我们有一些未配对的开括号可以与该闭括号配对。当1eft>0,也就是有未配对的左括号可用时就是这种情况。

  4. 如果我们在 left == 0时遇到一个闭括号例如 ,那么当前的表达式无效。否则,我们会减少 left 的值,也就是减少了可用的未配对的左括号的数量。

  5. 继续处理字符串,直到处理完所有括号。

  6. 如果最后我们仍然有未配对的左括号,这意味着表达式无效。

在这里讨论这个特定算法是因为我们从该解决方案中获得灵感以解决原始问题。为了更好地理解我们讨论的算法,请观看下面的动画演示。

如果我们对原始问题这个办法,这是根本就行不通的。基于简单计数器的方法能够在上面完美运行是因为所有括号都具有相同的类型。

因此,当我们遇到一个闭括号时,我们只需要假设有一个对应匹配的开括号是可用的,即假设 left > 0。

但是,在我们的问题中,如果我们遇到 ],我们真的不知道是否有相应的 [ 可用。你可能会问:

为什么不为不同类型的括号分别维护一个单独的计数器?

这可能不起作用,因为括号的相对位置在这里也很重要。例如:

[ { ]

如果我们只是在这里维持计数器,那么只要我们遇到闭合方括号,我们就会知道此处有一个可用的未配对的开口方括号。

但是,最近的未配对的开括号是一个花括号,而不是一个方括号,因此计数方法在这里被打破了。

方法一:栈

关于有效括号表达式的一个有趣属性是有效表达式的子表达式也应该是有效表达式。(不是每个子表达式)例如

仔细查看上述结构,颜色标识的单元格将标记开闭的括号对。整个表达式是有效的,而它的子表达式本身也是有效的。这为问题提供了一种递归结构。例如,考虑上图中两个绿色括号内的表达式。开括号位于索引 1,相应闭括号位于索引 6。

如果每当我们在表达式中遇到一对匹配的括号时,我们只是从表达式中删除它,会发生什么?

让我们看看下面的这个想法,从整体表达式中一次删除一个较小的表达式,因为这是一个有效的表达式,我们最后剩留下一个空字符串。

在表示问题的递归结构时,栈数据结构可以派上用场。我们无法真正地从内到外处理这个问题,因为我们对整体结构一无所知。但是,栈可以帮助我们递归地处理这种情况,即从外部到内部。

算法

  1. 初始化栈S。

  2. 依次处理表达式的每个括号,用一个栈来保存{

  3.当遍历到这三个字符的时候,就将其保存到栈中。

  4. 如果我们遇到一个闭括号,那么我们检查栈顶的元素。如果栈顶的元素是一个相同类型的左括号,那么我们将它从栈中弹出并继续处理。否则,这意味着表达式无效。

  5. 如果到最后我们剩下的栈中仍然有元素,那么这意味着表达式无效。

我们来看一下该算法的动画演示,然后转到实现部分。

现在让我们看看该算法是如何实现的。

class Solution {
public boolean isValid(String s) {
if(s==null || "".equals(s)) {
return true;
}
//用栈保存 (,[,{
Stack<Character> stack = new Stack<Character>();
//map中保存的是 ):(, ]:[,}:{
//当遍历到 )时候就会去map中找对应的value,也就是(
//再用这个value和stack弹出的元素比较,如果相等则匹配上,不等则返回false
//这里也可以用数组来存,为了简单就用map表示了
HashMap<Character,Character> map = new HashMap<Character,Character>();
map.put(')','(');
map.put(']','[');
map.put('}','{');
for(int i=0;i<s.length();i++) {
char c = s.charAt(i);
//如果map中不包含 (,[,{,就将这个字符放入栈中
if(!map.containsKey(c)) {
stack.add(c);
} else {
//如果遍历的字符不在map中,也就是说这个字符是),],},那么就要跟栈中的元素比较
//首先要判断栈是否为空,如果输入的字符是 )() ,那么当遍历到第一个)时,栈为空
if(stack.size()==0) {
return false;
}
//取出栈顶的元素
Character tmp = stack.pop();
//假设当前遍历到的元素是 ],那么从map中取到的value就是 [
//如果栈顶的元素是 (,则不匹配返回false,否则继续
if(map.get(c)!=tmp) {
return false;
}
}
}
//返回的时候还要判断栈是否为空
//如果输入的字符串是 (((,那么最后栈就不为空
return (stack.empty()? true : false);
}
}

LeetCode刷题--有效的括号(简单)的更多相关文章

  1. LeetCode刷题--整数反转(简单)

    题目描述 给出一个 32 位的有符号整数,你需要将这个整数中每位上的数字进行反转. 示例 1: 输入: 123 输出: 321 示例 2: 输入: -123 输出: -321 示例 3: 输入: 12 ...

  2. C#LeetCode刷题-栈

    栈篇 # 题名 刷题 通过率 难度 20 有效的括号 C#LeetCode刷题之#20-有效的括号(Valid Parentheses) 33.0% 简单 42 接雨水   35.6% 困难 71 简 ...

  3. C#LeetCode刷题-动态规划

    动态规划篇 # 题名 刷题 通过率 难度 5 最长回文子串   22.4% 中等 10 正则表达式匹配   18.8% 困难 32 最长有效括号   23.3% 困难 44 通配符匹配   17.7% ...

  4. C#LeetCode刷题-字符串

    字符串篇 # 题名 刷题 通过率 难度 3 无重复字符的最长子串   24.6% 中等 5 最长回文子串   22.4% 中等 6 Z字形变换   35.8% 中等 8 字符串转整数 (atoi)   ...

  5. LeetCode刷题总结之双指针法

    Leetcode刷题总结 目前已经刷了50道题,从零开始刷题学到了很多精妙的解法和深刻的思想,因此想按方法对写过的题做一个总结 双指针法 双指针法有时也叫快慢指针,在数组里是用两个整型值代表下标,在链 ...

  6. LeetCode刷题总结-数组篇(上)

    数组是算法中最常用的一种数据结构,也是面试中最常考的考点.在LeetCode题库中,标记为数组类型的习题到目前为止,已累计到了202题.然而,这202道习题并不是每道题只标记为数组一个考点,大部分习题 ...

  7. LeetCode刷题总结-数组篇(中)

    本文接着上一篇文章<LeetCode刷题总结-数组篇(上)>,继续讲第二个常考问题:矩阵问题. 矩阵也可以称为二维数组.在LeetCode相关习题中,作者总结发现主要考点有:矩阵元素的遍历 ...

  8. LeetCode刷题总结-数组篇(下)

    本期讲O(n)类型问题,共14题.3道简单题,9道中等题,2道困难题.数组篇共归纳总结了50题,本篇是数组篇的最后一篇.其他三个篇章可参考: LeetCode刷题总结-数组篇(上),子数组问题(共17 ...

  9. LeetCode刷题总结-树篇(中)

    本篇接着<LeetCode刷题总结-树篇(上)>,讲解有关树的类型相关考点的习题,本期共收录17道题,1道简单题,10道中等题,6道困难题. 在LeetCode题库中,考察到的不同种类的树 ...

随机推荐

  1. 使用Homebrew来安装Node等工具

    原文转载自:https://www.cnblogs.com/richard-youth/p/9718349.html 使用 React Native,必须安装的依赖有:Node.Watchman 和 ...

  2. WebRoot/WEBINF下的classes文件内无法生成编译文件,classes下没有文件,eclipse无法编译项目

    其实主要问题还是一个eclipse配置的问题. 如下图.将这一项的勾取消掉. 转自:https://blog.csdn.net/qq_36443497/article/details/79684231

  3. python 使用 RabbitMQ

    一.RabbitMQ消息队列介绍 RabbitMQ是在两个独立得python程序,或其他语言交互时使用. RabbitMQ:erlang语言 开发的. Python中连接RabbitMQ的模块:pik ...

  4. SQL入门经典(第四版)学习记录——欢迎来到SQL世界(一)

    1.结构化查询语言——SQL,关系型数据库通信的标准语言: 2.关系型数据库:表的逻辑单元组成,这些表在内部彼此关联,组成了关系型数据库: 3.SQL会话:用户用SQL命令语句与关系型数据库进行交互时 ...

  5. MySQL安装与连接

    1.安装 下载地址:https://dev.mysql.com/downloads/mysql/ 常见问题及解决办法:https://blog.csdn.net/chen97_08/article/d ...

  6. 排查java 内存CPU报警

    #!/bin/bash source /etc/profile #接收外部传入PID,任选一种 #servicePid=$1 headPid=`ps auxw|sort -rn -k3|head -4 ...

  7. Django 1.8.2 admin 数据库操作按下保存按钮出错

    Django报错:Runtimeerror: generator raised StopIteration python版本太新不兼容照成,下载python3.6就行了

  8. ASP.NET Core分布式项目-1.IdentityServer4登录中心

    源码下载 一.添加服务端的api 1.添加NUGet包 IdentityServer4 点击下载,重新生成 2.添加Startup配置 打开Startup文件 public class Startup ...

  9. 如何知道外围器件的器件地址PHY_ADDR

    由于 S5P6818 现有的uboot没有网卡的初始化代码实现,在尝试移植网卡驱动而跟踪有关的源码的时候,不止一次地看到了类似下面的需要修改器件地址的有关写法. 根据原理图的有关硬件连接,找到对应的元 ...

  10. ORACLE:锁被未决分布式事务处理 18.27.160617 持有

    1. 以管理员账号登陆 2. rollback  force '18.27.160617 ';