leecode題目20.-有效的括號

給定一個只包括 ‘(’,’)’,’{’,’}’,’[’,’]’ 的字符串,判斷字符串是否有效。

有效字符串需滿足:

左括號必須用相同類型的右括號閉合。
左括號必須以正確的順序閉合。
注意空字符串可被認爲是有效字符串。

示例 1:

輸入: “()”
輸出: true
示例 2:

輸入: “()[]{}”
輸出: true

思路

想象一下,你正在爲你的大學課設編寫一個小型編譯器,編譯器的任務之一(或稱子任務)將檢測括號是否匹配。
我們本文中看到的算法可用於處理編譯器正在編譯的程序中的所有括號,並檢查是否所有括號都已配對。這將檢查給定的括號字符串是否有效,是一個重要的編程問題。
我們這個問題中將要處理的表達式可以包含以下三種不同類型的括號:

(),
{} 以及
[]
在查看如何檢查由這些括號組成的給定表達式是否有效之前,讓我們看一下該問題的簡化版本,在簡化後的問題中,只含一種類型的括號。這麼一來,我們將會遇到的表達式是

(((((()))))) – VALID

()()()() – VALID

(((((((() – INVALID

((()(()))) – VALID
上我們試着用一個簡單的算法來解決這一問題。

我們從表達式的左側開始,每次只處理一個括號。
假設我們遇到一個開括號(即 (),表達式是否無效取決於在該表達式的其餘部分的某處是否有相匹配的閉括號(即 ))。此時,我們只是增加計數器的值保持跟蹤現在爲止開括號的數目。left += 1
如果我們遇到一個閉括號,這可能意味着這樣兩種情況:
此閉括號沒有與與之對應的開括號,在這種情況下,我們的表達式無效。當 left == 0,也就是沒有未配對的左括號可用時就是這種情況。
我們有一些未配對的開括號可以與該閉括號配對。當 left > 0,也就是有未配對的左括號可用時就是這種情況。
如果我們在 left == 0 時遇到一個閉括號(例如 )),那麼當前的表達式無效。否則,我們會減少 left 的值,也就是減少了可用的未配對的左括號的數量。
繼續處理字符串,直到處理完所有括號。
如果最後我們仍然有未配對的左括號,這意味着表達式無效。
在這裏討論這個特定算法是因爲我們從該解決方案中獲得靈感以解決原始問題。爲了更好地理解我們討論的算法,請觀看下面的動畫演示。

1 / 12
如果我們只是嘗試對原始問題採用相同的辦法,這是根本就行不通的。基於簡單計數器的方法能夠在上面完美運行是因爲所有括號都具有相同的類型。因此,當我們遇到一個閉括號時,我們只需要假設有一個對應匹配的開括號是可用的,即假設 left > 0。

但是,在我們的問題中,如果我們遇到 ],我們真的不知道是否有相應的 [ 可用。你可能會問:

爲什麼不爲不同類型的括號分別維護一個單獨的計數器?

這可能不起作用,因爲括號的相對位置在這裏也很重要。例如:

[{]
如果我們只是在這裏維持計數器,那麼只要我們遇到閉合方括號,我們就會知道此處有一個可用的未配對的開口方括號。但是,最近的未配對的開括號是一個花括號,而不是一個方括號,因此計數方法在這裏被打破了。

方法:棧
關於有效括號表達式的一個有趣屬性是有效表達式的子表達式也應該是有效表達式。(不是每個子表達式)例如

此外,如果仔細查看上述結構,顏色標識的單元格將標記開閉的括號對。整個表達式是有效的,而它的子表達式本身也是有效的。這爲問題提供了一種遞歸結構。例如,考慮上圖中兩個綠色括號內的表達式。開括號位於索引 1,相應閉括號位於索引 6。

如果每當我們在表達式中遇到一對匹配的括號時,我們只是從表達式中刪除它,會發生什麼?

讓我們看看下面的這個想法,從整體表達式中一次刪除一個較小的表達式,因爲這是一個有效的表達式,我們最後剩留下一個空字符串。

在表示問題的遞歸結構時,棧數據結構可以派上用場。我們無法真正地從內到外處理這個問題,因爲我們對整體結構一無所知。但是,棧可以幫助我們遞歸地處理這種情況,即從外部到內部。
讓我們看看使用棧作爲該問題的中間數據結構的算法。

算法

初始化棧 S。
一次處理表達式的每個括號。
如果遇到開括號,我們只需將其推到棧上即可。這意味着我們將稍後處理它,讓我們簡單地轉到前面的 子表達式。
如果我們遇到一個閉括號,那麼我們檢查棧頂的元素。如果棧頂的元素是一個 相同類型的 左括號,那麼我們將它從棧中彈出並繼續處理。否則,這意味着表達式無效。
如果到最後我們剩下的棧中仍然有元素,那麼這意味着表達式無效。

現在讓我們看看該算法是如何實現的。

class Solution {

  // Hash table that takes care of the mappings.
  private HashMap<Character, Character> mappings;

  // Initialize hash map with mappings. This simply makes the code easier to read.
  public Solution() {
    this.mappings = new HashMap<Character, Character>();
    this.mappings.put(')', '(');
    this.mappings.put('}', '{');
    this.mappings.put(']', '[');
  }

  public boolean isValid(String s) {

    // Initialize a stack to be used in the algorithm.
    Stack<Character> stack = new Stack<Character>();

    for (int i = 0; i < s.length(); i++) {
      char c = s.charAt(i);

      // If the current character is a closing bracket.
      if (this.mappings.containsKey(c)) {

        // Get the top element of the stack. If the stack is empty, set a dummy value of '#'
        char topElement = stack.empty() ? '#' : stack.pop();

        // If the mapping for this bracket doesn't match the stack's top element, return false.
        if (topElement != this.mappings.get(c)) {
          return false;
        }
      } else {
        // If it was an opening bracket, push to the stack.
        stack.push(c);
      }
    }

    // If the stack still contains elements, then it is an invalid expression.
    return stack.isEmpty();
  }
}

複雜度分析

時間複雜度:O(n)O(n),因爲我們一次只遍歷給定的字符串中的一個字符並在棧上進行 O(1)O(1) 的推入和彈出操作。
空間複雜度:O(n)O(n),當我們將所有的開括號都推到棧上時以及在最糟糕的情況下,我們最終要把所有括號推到棧上。例如 ((((((((((。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章