每天 3 分钟,走上算法的逆袭之路。

前文合集

每日一道 LeetCode 前文合集

代码仓库

GitHub: https://github.com/meteor1993/LeetCode

Gitee: https://gitee.com/inwsy/LeetCode

题目:有效的括号

题目来源:https://leetcode-cn.com/problems/valid-parentheses/

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

有效字符串需满足:

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

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

示例 1:

输入: "()"
输出: true

示例 2:

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

示例 3:

输入: "(]"
输出: false

示例 4:

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

示例 5:

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

解题思路

解题思路?

啊呸,有个 P 的解题思路,我跟你们讲哦,这种题没见过,你就是想不出来。

我自己思索了几分钟以后,果断投降去看答案了,这玩意,不是我这种新手小白搞的定的。

结果,一看答案秒懂。

整个解题思路是借用了数据结构「栈」的「先进后出」的特性。

首先第一件事情还是先思考清楚极限情况,比如如果是空字符串,是可以返回 true ,如果字符串长度是奇数,那么显然是无法满足左右括号的对应关系。

接下来就是核心问题,一个左括号一个右括号,中间还可能隔着千山万水,如何处理?

使用「栈」结构:

  • 遇到左括号就压入栈。
  • 遇到右括号就和栈顶的左括号比对,匹配失败直接返回 false 。
  • 如果匹配成功,则可能是嵌套在其它匹配括号中的,所以此时要将当前栈顶的左括号弹出。
  • 如果最后最终,栈中没有剩余元素,也就是没有剩下左括号,说明刚好完成匹配,括号字符串有效。否则匹配失败,括号字符串无效。

如果上面这一段不好理解,可以借助下面这个动图(来源:LeetCode):

代码实现

有了上面的思路,写代码都是小事儿了,先看一个我自己写的,完全符合上面的逻辑:

public boolean isValid(String s) {
if (s.length() == 0) return true; if (s.length() % 2 == 1) return false; Stack<Character> stack = new Stack<> (); for (int i = 0; i < s.length(); i++) {
char charAt = s.charAt(i); // 如果是左括号,则把字符压入栈
if (charAt == '(' || charAt == '{' || charAt == '[')
stack.push(charAt);
else {
// 如果此时还有右括号而栈中已无左括号
if (stack.isEmpty()) return false;
// 获取栈顶的值
char top = stack.peek();
// 如果栈顶的值等于右括号,则出栈,否则返回 false
if ((top == '{' && charAt == '}') || (top == '(' && charAt == ')') || (top == '[' && charAt == ']'))
stack.pop();
else
return false;
}
}
return stack.isEmpty();
}

里面的注释已经比较清晰了,我就不多解释了。

接着我看了官方提供的答案,基本上思路和我的代码保持一致,只是把左右括号放入到了哈希表中,由哈希表来判断括号是否存在。

private Map<Character, Character> map;

// 初始化哈希表
public Solution() {
this.map = new HashMap<> ();
this.map.put(')', '(');
this.map.put('}', '{');
this.map.put(']', '[');
} public boolean isValid_1(String s) {
if (s.length() == 0) return true; if (s.length() % 2 == 1) return false; Stack<Character> stack = new Stack<> (); for (int i = 0; i < s.length(); i++) {
char charAt = s.charAt(i);
// 如果不是右括号,则把字符压入栈
if (!this.map.containsKey(charAt)) {
stack.push(charAt);
} else {
// 如果此时还有右括号而栈中已无左括号
if (stack.isEmpty()) return false;
// 获取栈顶的值
char top = stack.peek();
// 如果栈顶的值等于右括号,则出栈,否则返回 false
if (top == this.map.get(charAt))
stack.pop();
else
return false;
}
}
return stack.empty();
}

因为哈希表中的数据太少,而且寻址的次数也不够多,所以哈希表的方案看起来还比顺次匹配的的方案耗时高。

效率更高的方案有没有,当然有,直接使用数组模拟栈的入栈和出栈,这个时间是可以压缩在 1ms 以内的,执行时间可以在 Java 的提交中击败 100% 的用户,这个示例我就不写了,感兴趣的同学可以自己写写看。

在我翻答案的时候看到一个 1ms 的方案,这个方案是对我前面的那个 2ms 的代码的优化,有点意思,拿出来聊一下:

public boolean isValid_2(String s) {
char[] chs = s.toCharArray();
Stack<Character> stack = new Stack<>();
for (char c : chs) {
if (c == '{') {
stack.push('}');
} else if (c == '[') {
stack.push(']');
} else if (c == '(') {
stack.push(')');
} else if (stack.isEmpty() || stack.pop() != c) {
return false;
}
}
return stack.isEmpty();
}

这个方案就是在遇到左括号的时候往栈里面放一个对应的右括号,这么做的原因我猜测是为了好判断,只需要取出的时候和当前循环的右括号做一次 == 判断,如果相等就继续循环,不相等就直接返回 false 了。

不得不说这个方案的构思很巧妙,在已有的方案上做了相当极致的优化,把耗时进一步降低。果然姜还是老的辣,你大爷还是你大爷。

果然是学无止境,希望各位刷 LeetCode 的同学,如果时间充足,也可以多看看不同的答案,对开阔思路和视野真的是相当有帮助。

顺便吐槽下:想到这些方案的大神太 TM 变态了,这个脑回路和常人差的太远了,能用数组把耗时压到 1ms 以内我能接受,但是能把栈这种数据结构的方案优化到 2ms 以内,这个我是真的服。

每日一道 LeetCode (6):有效的括号的更多相关文章

  1. 每日一道 LeetCode (3):回文数

    前文合集 每日一道 LeetCode 文章合集 题目:回文数 题目来源:https://leetcode-cn.com/problems/palindrome-number/ 判断一个整数是否是回文数 ...

  2. 每日一道 LeetCode (5):最长公共前缀

    前文合集 每日一道 LeetCode 前文合集 代码仓库 GitHub: https://github.com/meteor1993/LeetCode Gitee: https://gitee.com ...

  3. 每日一道 LeetCode (8):删除排序数组中的重复项和移除元素

    每天 3 分钟,走上算法的逆袭之路. 前文合集 每日一道 LeetCode 前文合集 代码仓库 GitHub: https://github.com/meteor1993/LeetCode Gitee ...

  4. 每日一道 LeetCode (9):实现 strStr()

    每天 3 分钟,走上算法的逆袭之路. 前文合集 每日一道 LeetCode 前文合集 代码仓库 GitHub: https://github.com/meteor1993/LeetCode Gitee ...

  5. 每日一道 LeetCode (10):搜索插入位置

    每天 3 分钟,走上算法的逆袭之路. 前文合集 每日一道 LeetCode 前文合集 代码仓库 GitHub: https://github.com/meteor1993/LeetCode Gitee ...

  6. 每日一道 LeetCode (14):数组加一

    每天 3 分钟,走上算法的逆袭之路. 前文合集 每日一道 LeetCode 前文合集 代码仓库 GitHub: https://github.com/meteor1993/LeetCode Gitee ...

  7. 每日一道 LeetCode (15):二进制求和

    每天 3 分钟,走上算法的逆袭之路. 前文合集 每日一道 LeetCode 前文合集 代码仓库 GitHub: https://github.com/meteor1993/LeetCode Gitee ...

  8. 每日一道 LeetCode (19):合并两个有序数组

    每天 3 分钟,走上算法的逆袭之路. 前文合集 每日一道 LeetCode 前文合集 代码仓库 GitHub: https://github.com/meteor1993/LeetCode Gitee ...

  9. 每日一道 LeetCode (41):阶乘后的零

    每天 3 分钟,走上算法的逆袭之路. 前文合集 每日一道 LeetCode 前文合集 代码仓库 GitHub: https://github.com/meteor1993/LeetCode Gitee ...

随机推荐

  1. redis源码学习之工作流程初探

    目录 背景 环境准备 下载redis源码 下载Visual Studio Visual Studio打开redis源码 启动过程分析 调用关系图 事件循环分析 工作模型 代码分析 动画演示 网络模块 ...

  2. python 并发专题(二):python线程以及线程池相关以及实现

    一 多线程实现 线程模块 - 多线程主要的内容:直接进行多线程操作,线程同步,带队列的多线程: Python3 通过两个标准库 _thread 和 threading 提供对线程的支持. _threa ...

  3. python 装饰器(三):装饰器实例(一)

    示例 7-15 定义了一个装饰器,它会在每次调用被装饰的函数时计时,然后把经过的时间.传入的参数和调用的结果打印出来.示例 7-15 一个简单的装饰器,输出函数的运行时间 import time de ...

  4. TX 1核4G2M云服务器,376/2年,可免费续1年

    腾讯云个人开发者活动 https://cloud.tencent.com/act/developer

  5. 从对象到类,Java中需要知道的这些东西

    1. 对象的诞生   在平时的开发中,我们使用对象的时候,都是直接new一个临时变量然后进行各种逻辑赋值然后返回,但是你有没有想过一个对象在创建的过程中经历了什么呢,为什么创建时静态变量就已经赋完值了 ...

  6. (3)html-webpack-plugin的作用

    在内存中生成index.html页面 在前面的内容中我们已经知道了如何在内存中打包main.js并引入到页面中. 同样的,我们也可以把index.html也打包放入到内存中. 安装html-webpa ...

  7. Ethical Hacking - Web Penetration Testing(5)

    LOCAL FILE INCLUSION Allows an attacker to read ANY file on the same server. Access files outside ww ...

  8. P2058 海港 (洛谷)

    这个题复制过来真的有点恶心,懒得手打,以后再搬题面吧. 今天我双更了,AC这个题我就完成某谷春令营第一课的作业了(假的) 这个题是个双指针.非常友善.一直往里读入就可以了,遇见不是一条船的乘客输出这一 ...

  9. 深入了解PHP的生成器

    在驾驶方面,速度并不会决定一切.但是在网络上,速度至关重要.你的应用程序越快,用户体验就越好.好吧,这时候有人就奇怪了,本文是关于PHP 生成器的,那么为什么我们要谈论速度呢?很快你就会发现,生成器在 ...

  10. [日常摘要] -- zookeeper篇

    概览 设计目标 是将那些复杂且容易出错的分布式一致性服务封装起来,构成一个高效可靠的原语集,并以一系列简单易用的接口提供给用户使用 简介 是一个典型的分布式数据一致性解决方案,分布式应用程序可以基于Z ...