題目描述
給定一個只包括 ' ( ' , ' ) ', ' { ' , ' } ' , ' [ ' , ' ] ' 的字符串,判斷字符串是否有效。
有效字符串需滿足:
- 左括號必須用相同類型的右括號閉合。
- 左括號必須以正確的順序閉合。
注意空字符串可被認為是有效字符串。
示例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); } }