给定一个只包括 ‘(’,‘)’,‘{’,‘}’,‘[’,‘]’ 的字符串s
,判断字符串是否有效。
有效字符串需满足:
【1】左括号必须用相同类型的右括号闭合。
【2】左括号必须以正确的顺序闭合。
【3】每个右括号都有一个对应的相同类型的左括号。
示例 1:
输入:s = “()”
输出:true
示例 2:
输入:s = “()[]{}”
输出:true
示例 3:
输入:s = “(]”
输出:false
1 <= s.length <= 104
s 仅由括号 ‘()[]{}’ 组成
判断括号的有效性可以使用「栈」这一数据结构来解决。
我们遍历给定的字符串s
。当我们遇到一个左括号时,我们会期望在后续的遍历中,有一个相同类型的右括号将其闭合。由于后遇到的左括号要先闭合,因此我们可以将这个左括号放入栈顶。
当我们遇到一个右括号时,我们需要将一个相同类型的左括号闭合。此时,我们可以取出栈顶的左括号并判断它们是否是相同类型的括号。如果不是相同的类型,或者栈中并没有左括号,那么字符串s
无效,返回False
。为了快速判断括号的类型,我们可以使用哈希表存储每一种括号。哈希表的键为右括号,值为相同类型的左括号。
在遍历结束后,如果栈中没有左括号,说明我们将字符串s
中的所有左括号闭合,返回True
,否则返回False
。
注意到有效字符串的长度一定为偶数,因此如果字符串的长度为奇数,我们可以直接返回False
,省去后续的遍历判断过程。
class Solution {
public boolean isValid(String s) {
// 判断是否为偶数,不是直接推出
if (s == null || s.trim().length() <= 0 || (s.trim().length() & 1) == 1 ) {
return false;
}
// 将 '(',')','{','}','[',']' 存放之 map中
Map<Character, Character> pairs = new HashMap<Character, Character>();
pairs.put(')','(');
pairs.put('}','{');
pairs.put(']','[');
// 维护一个栈
Deque<Character> stack = new LinkedList<Character>();
for (int i = 0; i < s.length(); i++) {
// 如果拿到了一个闭环的括号,则比较栈和集合中的元素是否相同
if (pairs.containsKey(s.charAt(i))) {
// peek 方法时查看头部元素,不影响栈的结构,不能使用pop,会影响栈的结构
if (stack.isEmpty() || stack.peek() != pairs.get(s.charAt(i))) {
return false;
}
// 如果相同,则将栈中元素弹出
stack.pop();
} else {
// 如果不是闭环,则压栈
stack.push(s.charAt(i));
}
}
return stack.isEmpty();
}
}
时间复杂度: O(n)
,其中n
是字符串s
的长度。
空间复杂度: O(n+∣Σ∣)
,其中Σ
表示字符集,本题中字符串只包含6
种括号,∣Σ∣=6
。栈中的字符数量为O(n)
,而哈希表使用的空间为O(∣Σ∣)
,相加即可得到总空间复杂度。
C++
精简版
class Solution {
public:
bool isValid(string s) {
stack st;
for (int i = 0; i < s.size(); i++) {
if (s[i] == '(' || s[i] == '[' || s[i] == '{') st.push(i);
else {
if (st.empty()) return false;
if (s[i] == ')' && s[st.top()] != '(') return false;
if (s[i] == '}' && s[st.top()] != '{') return false;
if (s[i] == ']' && s[st.top()] != '[') return false;
st.pop();
}
}
return st.empty();
}
};
高级版解题思路: 栈先入后出特点恰好与本题括号排序特点一致,即若遇到左括号入栈,遇到右括号时将对应栈顶左括号出栈,则遍历完所有括号后stack
仍然为空;建立哈希表dic
构建左右括号对应关系:key
左括号,value
右括号;这样查询2
个括号是否对应只需O(1)
时间复杂度;建立栈stack
,遍历字符串s
并按照算法流程一一判断。
算法流程: 如果c
是左括号,则入栈push
;否则通过哈希表判断括号对应关系,若stack
栈顶出栈括号stack.pop()
与当前遍历括号c
不对应,则提前返回false
。
**提前返回优点:**在迭代过程中,提前发现不符合的括号并且返回,提升算法效率。
解决边界问题: 栈stack
为空: 此时stack.pop()
操作会报错;因此,我们采用一个取巧方法,给stack
赋初值?
,并在哈希表dic
中建立key:′?′
,value:′?′
的对应关系予以配合。此时当stack
为空且c
为右括号时,可以正常提前返回false
字符串s
以左括号结尾: 此情况下可以正常遍历完整个s
,但stack
中遗留未出栈的左括号;因此,最后需返回len(stack) == 1
,以判断是否是有效的括号组合。
class Solution {
private static final Map<Character,Character> map = new HashMap<Character,Character>(){{
put('{','}'); put('[',']'); put('(',')'); put('?','?');
}};
public boolean isValid(String s) {
if(s.length() > 0 && !map.containsKey(s.charAt(0))) return false;
LinkedList<Character> stack = new LinkedList<Character>() {{ add('?'); }};
for(Character c : s.toCharArray()){
if(map.containsKey(c)) stack.addLast(c);
else if(map.get(stack.removeLast()) != c) return false;
}
return stack.size() == 1;
}
}
时间复杂度: O(N)
:正确的括号组合需要遍历1
遍s
;
空间复杂度: O(N)
:哈希表和栈使用线性的空间大小;