数据结构与算法应用往往隐藏在我们看不到的地方
20. 有效的括号
给定一个只包括 '(',')','{','}','[',']' 的字符串,判断字符串是否有效。
有效字符串需满足:
- 左括号必须用相同类型的右括号闭合。
- 左括号必须以正确的顺序闭合。
- 注意空字符串可被认为是有效字符串。
示例 1:
- 输入: "()"
- 输出: true
示例 2:
- 输入: "()[]{}"
- 输出: true
示例 3:
- 输入: "(]"
- 输出: false
示例 4:
- 输入: "([)]"
- 输出: false
示例 5:
- 输入: "{[]}"
- 输出: true
思路
题外话
括号匹配是使用栈解决的经典问题。
题意其实就像我们在写代码的过程中,要求括号的顺序是一样的,有左括号,相应的位置必须要有右括号。
如果还记得编译原理的话,编译器在 词法分析的过程中处理括号、花括号等这个符号的逻辑,也是使用了栈这种数据结构。
再举个例子,linux系统中,cd这个进入目录的命令我们应该再熟悉不过了。
cd a/b/c/../../
这个命令最后进入a目录,系统是如何知道进入了a目录呢 ,这就是栈的应用(其实可以出一道相应的面试题了)
所以栈在计算机领域中应用是非常广泛的。
有的同学经常会想学的这些数据结构有什么用,也开发不了什么软件,大多数同学说的软件应该都是可视化的软件例如APP、网站之类的,那都是非常上层的应用了,底层很多功能的实现都是基础的数据结构和算法。
所以数据结构与算法的应用往往隐藏在我们看不到的地方!
这里我就不过多展开了,先来看题。
进入正题
由于栈结构的特殊性,非常适合做对称匹配类的题目。
首先要弄清楚,字符串里的括号不匹配有几种情况。
一些同学,在面试中看到这种题目上来就开始写代码,然后就越写越乱。
建议在写代码之前要分析好有哪几种不匹配的情况,如果不在动手之前分析好,写出的代码也会有很多问题。
先来分析一下 这里有三种不匹配的情况,
-
第一种情况,字符串里左方向的括号多余了 ,所以不匹配。
-
第二种情况,括号没有多余,但是 括号的类型没有匹配上。
-
第三种情况,字符串里右方向的括号多余了,所以不匹配。
我们的代码只要覆盖了这三种不匹配的情况,就不会出问题,可以看出 动手之前分析好题目的重要性。
动画如下:
第一种情况:已经遍历完了字符串,但是栈不为空,说明有相应的左括号没有右括号来匹配,所以return false
第二种情况:遍历字符串匹配的过程中,发现栈里没有要匹配的字符。所以return false
第三种情况:遍历字符串匹配的过程中,栈已经为空了,没有匹配的字符了,说明右括号没有找到对应的左括号return false
那么什么时候说明左括号和右括号全都匹配了呢,就是字符串遍历完之后,栈是空的,就说明全都匹配了。
分析完之后,代码其实就比较好写了,
但还有一些技巧,在匹配左括号的时候,右括号先入栈,就只需要比较当前元素和栈顶相不相等就可以了,比左括号先入栈代码实现要简单的多了!
实现C++代码如下:
class Solution {
public:
bool isValid(string s) {
if (s.size() % 2 != 0) return false; // 如果s的长度为奇数,一定不符合要求
stack<char> st;
for (int i = 0; i < s.size(); i++) {
if (s[i] == '(') st.push(')');
else if (s[i] == '{') st.push('}');
else if (s[i] == '[') st.push(']');
// 第三种情况:遍历字符串匹配的过程中,栈已经为空了,没有匹配的字符了,说明右括号没有找到对应的左括号 return false
// 第二种情况:遍历字符串匹配的过程中,发现栈里没有我们要匹配的字符。所以return false
else if (st.empty() || st.top() != s[i]) return false;
else st.pop(); // st.top() 与 s[i]相等,栈弹出元素
}
// 第一种情况:此时我们已经遍历完了字符串,但是栈不为空,说明有相应的左括号没有右括号来匹配,所以return false,否则就return true
return st.empty();
}
};
- 时间复杂度: O(n)
- 空间复杂度: O(n)
技巧性的东西没有固定的学习方法,还是要多看多练,自己灵活运用了。
其他语言版本
Java:
class Solution {
public boolean isValid(String s) {
Deque<Character> deque = new LinkedList<>();
char ch;
for (int i = 0; i < s.length(); i++) {
ch = s.charAt(i);
//碰到左括号,就把相应的右括号入栈
if (ch == '(') {
deque.push(')');
}else if (ch == '{') {
deque.push('}');
}else if (ch == '[') {
deque.push(']');
} else if (deque.isEmpty() || deque.peek() != ch) {
return false;
}else {//如果是右括号判断是否和栈顶元素匹配
deque.pop();
}
}
//最后判断栈中元素是否匹配
return deque.isEmpty();
}
}
Python:
# 方法一,仅使用栈,更省空间
class Solution:
def isValid(self, s: str) -> bool:
stack = []
for item in s:
if item == '(':
stack.append(')')
elif item == '[':
stack.append(']')
elif item == '{':
stack.append('}')
elif not stack or stack[-1] != item:
return False
else:
stack.pop()
return True if not stack else False
# 方法二,使用字典
class Solution:
def isValid(self, s: str) -> bool:
stack = []
mapping = {
'(': ')',
'[': ']',
'{': '}'
}
for item in s:
if item in mapping.keys():
stack.append(mapping[item])
elif not stack or stack[-1] != item:
return False
else:
stack.pop()
return True if not stack else False
Go:
// 思路: 使用栈来进行括号的匹配
// 时间复杂度 O(n)
// 空间复杂度 O(n)
func isValid(s string) bool {
// 使用切片模拟栈的行为
stack := make([]rune, 0)
// m 用于记录某个右括号对应的左括号
m := make(map[rune]rune)
m[')'] = '('
m[']'] = '['
m['}'] = '{'
// 遍历字符串中的 rune
for _, c := range s {
// 左括号直接入栈
if c == '(' || c == '[' || c == '{' {
stack = append(stack, c)
} else {
// 如果是右括号,先判断栈内是否还有元素
if len(stack) == 0 {
return false
}
// 再判断栈顶元素是否能够匹配
peek := stack[len(stack)-1]
if peek != m[c] {
return false
}
// 模拟栈顶弹出
stack = stack[:len(stack)-1]
}
}
// 若栈中不再包含元素,则能完全匹配
return len(stack) == 0
}
Ruby:
def is_valid(strs)
symbol_map = {')' => '(', '}' => '{', ']' => '['}
stack = []
strs.size.times {|i|
c = strs[i]
if symbol_map.has_key?(c)
top_e = stack.shift
return false if symbol_map[c] != top_e
else
stack.unshift(c)
end
}
stack.empty?
end
Javascript:
var isValid = function (s) {
const stack = [];
for (let i = 0; i < s.length; i++) {
let c = s[i];
switch (c) {
case '(':
stack.push(')');
break;
case '[':
stack.push(']');
break;
case '{':
stack.push('}');
break;
default:
if (c !== stack.pop()) {
return false;
}
}
}
return stack.length === 0;
};
// 简化版本
var isValid = function(s) {
const stack = [],
map = {
"(":")",
"{":"}",
"[":"]"
};
for(const x of s) {
if(x in map) {
stack.push(x);
continue;
};
if(map[stack.pop()] !== x) return false;
}
return !stack.length;
};
TypeScript:
版本一:普通版
function isValid(s: string): boolean {
let helperStack: string[] = [];
for (let i = 0, length = s.length; i < length; i++) {
let x: string = s[i];
switch (x) {
case '(':
helperStack.push(')');
break;
case '[':
helperStack.push(']');
break;
case '{':
helperStack.push('}');
break;
default:
if (helperStack.pop() !== x) return false;
break;
}
}
return helperStack.length === 0;
};
版本二:优化版
function isValid(s: string): boolean {
type BracketMap = {
[index: string]: string;
}
let helperStack: string[] = [];
let bracketMap: BracketMap = {
'(': ')',
'[': ']',
'{': '}'
}
for (let i of s) {
if (bracketMap.hasOwnProperty(i)) {
helperStack.push(bracketMap[i]);
} else if (i !== helperStack.pop()) {
return false;
}
}
return helperStack.length === 0;
};
Swift:
func isValid(_ s: String) -> Bool {
var stack = [String.Element]()
for ch in s {
if ch == "(" {
stack.append(")")
} else if ch == "{" {
stack.append("}")
} else if ch == "[" {
stack.append("]")
} else {
let top = stack.last
if ch == top {
stack.removeLast()
} else {
return false
}
}
}
return stack.isEmpty
}
C:
//辅助函数:判断栈顶元素与输入的括号是否为一对。若不是,则返回False
int notMatch(char par, char* stack, int stackTop) {
switch(par) {
case ']':
return stack[stackTop - 1] != '[';
case ')':
return stack[stackTop - 1] != '(';
case '}':
return stack[stackTop - 1] != '{';
}
return 0;
}
bool isValid(char * s){
int strLen = strlen(s);
//开辟栈空间
char stack[5000];
int stackTop = 0;
//遍历字符串
int i;
for(i = 0; i < strLen; i++) {
//取出当前下标所对应字符
char tempChar = s[i];
//若当前字符为左括号,则入栈
if(tempChar == '(' || tempChar == '[' || tempChar == '{')
stack[stackTop++] = tempChar;
//若当前字符为右括号,且栈中无元素或右括号与栈顶元素不符,返回False
else if(stackTop == 0 || notMatch(tempChar, stack, stackTop))
return 0;
//当前字符与栈顶元素为一对括号,将栈顶元素出栈
else
stackTop--;
}
//若栈中有元素,返回False。若没有元素(stackTop为0),返回True
return !stackTop;
}
C#:
public class Solution {
public bool IsValid(string s) {
var len = s.Length;
if(len % 2 == 1) return false; // 字符串长度为单数,直接返回 false
// 初始化栈
var stack = new Stack<char>();
// 遍历字符串
for(int i = 0; i < len; i++){
// 当字符串为左括号时,进栈对应的右括号
if(s[i] == '('){
stack.Push(')');
}else if(s[i] == '['){
stack.Push(']');
}else if(s[i] == '{'){
stack.Push('}');
}
// 当字符串为右括号时,当栈为空(无左括号) 或者 出栈字符不是当前的字符
else if(stack.Count == 0 || stack.Pop() != s[i])
return false;
}
// 如果栈不为空,例如“((()”,右括号少于左括号,返回false
if (stack.Count > 0)
return false;
// 上面的校验都满足,则返回true
else
return true;
}
}
PHP:
// https://www.php.net/manual/zh/class.splstack.php
class Solution
{
function isValid($s){
$stack = new SplStack();
for ($i = 0; $i < strlen($s); $i++) {
if ($s[$i] == "(") {
$stack->push(')');
} else if ($s[$i] == "{") {
$stack->push('}');
} else if ($s[$i] == "[") {
$stack->push(']');
// 2、遍历匹配过程中,发现栈内没有要匹配的字符 return false
// 3、遍历匹配过程中,栈已为空,没有匹配的字符了,说明右括号没有找到对应的左括号 return false
} else if ($stack->isEmpty() || $stack->top() != $s[$i]) {
return false;
} else {//$stack->top() == $s[$i]
$stack->pop();
}
}
// 1、遍历完,但是栈不为空,说明有相应的括号没有被匹配,return false
return $stack->isEmpty();
}
}
Scala:
object Solution {
import scala.collection.mutable
def isValid(s: String): Boolean = {
if(s.length % 2 != 0) return false // 如果字符串长度是奇数直接返回false
val stack = mutable.Stack[Char]()
// 循环遍历字符串
for (i <- s.indices) {
val c = s(i)
if (c == '(' || c == '[' || c == '{') stack.push(c)
else if(stack.isEmpty) return false // 如果没有(、[、{则直接返回false
// 以下三种情况,不满足则直接返回false
else if(c==')' && stack.pop() != '(') return false
else if(c==']' && stack.pop() != '[') return false
else if(c=='}' && stack.pop() != '{') return false
}
// 如果为空则正确匹配,否则还有余孽就不匹配
stack.isEmpty
}
}
Rust:
impl Solution {
pub fn is_valid(s: String) -> bool {
if s.len() % 2 == 1 {
return false;
}
let mut stack = vec![];
let mut chars: Vec<char> = s.chars().collect();
while let Some(s) = chars.pop() {
match s {
')' => stack.push('('),
']' => stack.push('['),
'}' => stack.push('{'),
_ => {
if stack.is_empty() || stack.pop().unwrap() != s {
return false;
}
}
}
}
stack.is_empty()
}
}