力扣20这道题在2020年非常火,B站、小米、搜狐等大厂都在面试中考察了这道题,由此可见此题的重要性。
此题虽然是简单题,但是我在做的时候,并没有像其他简单题一样,根据题目表述逻辑顺利地写出来,而是花了不少时间来思考才慢慢找到逻辑。在考虑什么样的括号才算是合适的括号时,想到一个很关键的点,就是在遍历字符串时,我们后遇到的左括号要先闭合。敏感的同学可能已经想到了,这不就是栈的特性,后进先出吗?于是,在我把逻辑基础往【栈】这个数据结构上引后,才慢慢理清思路。
基于python,本文使用两个方法解决此题,并在文末讨论代码的优越性。
解法一:【栈】的解法
- 创建一个空的栈stack。
- 遍历字符串。
- 遇到左括号,就把这个字符串放在stack里。
- 遇到右括号,就把stack里顶层的元素拿出来,看看和这个右括号是否为同一类型的括号。是则继续,不是则返回False。
- 直到遍历完字符串的所有元素,看stack是否还剩着左括号没被取出来。有则返回False,没有则说明一切顺利,返回True。
class Solution:
def isValid(self, s: str) -> bool:
stack = [] # 创建一个空的栈stack
try:
for i in range(len(s)): # 遍历字符串
# 遇到左括号,就把这个字符串放在stack里
if s[i] == '(' or s[i] == '{' or s[i] == '[':
stack.append(s[i])
# 遇到右括号,就把stack里顶层的元素拿出来,看看和这个右括号是否为同一类型的括号。是则继续,不是则返回False
elif s[i] == ')' or s[i] == '}' or s[i] == ']':
temp = stack.pop()
if temp == '(' and s[i] != ')':
return False
elif temp == '[' and s[i] != ']':
return False
elif temp == '{' and s[i] != '}':
return False
# 直到遍历完字符串的所有元素,看stack是否还剩着左括号没被取出来。有则返回False,没有则说明一切顺利,返回True
if len(stack) == 0:
return True
else:
return False
except:
return False
注意,这里用到了python的异常处理结构try和except,是因为如果数组的第一个括号就是右括号,则temp = stack.pop()会报错,因为stack此时是空的。所以这种情况报错的话直接返回False,这不是有效的括号。
复杂度分析:
- 时间复杂度:O(N),其中N为字符串s的长度。
- 空间复杂度:O(n),其中n为字符串s的长度。
- 执行用时:28ms, 99.0%
- 内存消耗:14.9MB, 33.5%
解法二:
此方法来自LeetCode评论区的wings大神。有效的括号总有一个特点,那就是总有一对相邻的括号存在。他利用这一特点,仅用3行replace代码把括号由内到外层层拨开,来验证括号的有效性。思路巧妙至极,我看完只好再次大呼内行...
class Solution:
def isValid(self, s):
while '{}' in s or '()' in s or '[]' in s:
s = s.replace('{}', '')
s = s.replace('[]', '')
s = s.replace('()', '')
return s == ''
复杂度分析:
- 时间复杂度:O(N^2),其中N为字符串s的长度。
- 空间复杂度:O(n),其中n为字符串s的长度。
- 执行用时:64ms, 6.91%
- 内存消耗:14.9MB, 33.5%
关于代码优越性的思考
对比一下上面的两个解法。从思路上讲,解法一就是利用栈的常规思路,解法二更加精妙讨巧;从代码长度上讲,解法一的主体部分是19行,而解法二是5行,几乎是解法一的四分之一,对程序员来说写得更快;从执行效率上讲,解法二由于在一个while循环中用来3次replace方法,相当于每次循环都遍历字符串3次,所以解法二的实际执行效率要明显弱于解法一。
其实最开始我执行解法二的时候,看到执行时间明显下降,第一反应有些疑惑,因为这不符合我的直观印象。我们总觉得,越短的代码,设计越精妙的代码,理所当然有着更高的执行效率。但是,其实我们都忽略了一个问题,尤其是在python这种比较高级的语言中,这些看似简单的代码背后其实是有其他人已经写好了很多代码,打包成了这么一个方法。比如这里的replace看似简单,实际上这也是一个遍历字符串的循环。
所以,短而巧不能代表更好的执行效率。不否认这些花式解法要比暴力解法优美得多,有更高的艺术性。但是在实际应用中,可能低复杂度、可读性好的代码会有更好的实用性。
感谢各路大佬对每一道LeetCode题提供的各种思路,我们永远在追求兼具艺术性和实用性的代码的路上!
(如果两者一定要trade off,还是选择执行效率高的写法吧嘿嘿)