1. 程式人生 > >leetcode 20.有效的括號

leetcode 20.有效的括號

解決方案

**思路**

想象一下,你正在為你的大學課設編寫一個小型編譯器,編譯器的任務之一(或稱子任務)將檢測括號是否匹配。

我們本文中看到的演算法可用於處理編譯器正在編譯的程式中的所有括號,並檢查是否所有括號都已配對。這將檢查給定的括號字串是否有效,是一個重要的程式設計問題。

我們這個問題中將要處理的表示式可以包含以下三種不同型別的括號:

  • ()
  • {} 以及
  • []

在檢視如何檢查由這些括號組成的給定表示式是否有效之前,讓我們看一下該問題的簡化版本,在簡化後的問題中,只含一種型別的括號。這麼一來,我們將會遇到的表示式是

(((((()))))) -- VALID

()()()()     -- VALID

(((((((()    -- INVALID

((()(())))   -- VALID

上我們試著用一個簡單的演算法來解決這一問題。

  1. 我們從表示式的左側開始,每次只處理一個括號。
  2. 假設我們遇到一個開括號(即 (),表示式是否無效取決於在該表示式的其餘部分的某處是否有相匹配的閉括號(即 ))。此時,我們只是增加計數器的值保持跟蹤現在為止開括號的數目。left += 1
  3. 如果我們遇到一個閉括號,這可能意味著這樣兩種情況:
    1. 此閉括號沒有與與之對應的開括號,在這種情況下,我們的表示式無效。當 left == 0,也就是沒有未配對的左括號可用時就是這種情況。
    2. 我們有一些未配對的開括號可以與該閉括號配對。當 left > 0,也就是有未配對的左括號可用時就是這種情況。
  4. 如果我們在 left == 0
     時遇到一個閉括號(例如 )),那麼當前的表示式無效。否則,我們會減少 left 的值,也就是減少了可用的未配對的左括號的數量。
  5. 繼續處理字串,直到處理完所有括號。
  6. 如果最後我們仍然有未配對的左括號,這意味著表示式無效。

在這裡討論這個特定演算法是因為我們從該解決方案中獲得靈感以解決原始問題。為了更好地理解我們討論的演算法,請觀看下面的動畫演示。

1 / 12

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

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

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

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

[{]

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

方法:棧

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

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

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

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

1 / 5

在表示問題的遞迴結構時,棧資料結構可以派上用場。我們無法真正地從內到外處理這個問題,因為我們對整體結構一無所知。但是,棧可以幫助我們遞迴地處理這種情況,即從外部到內部。

讓我們看看使用棧作為該問題的中間資料結構的演算法。

演算法

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

我們來看一下該演算法的動畫演示,然後轉到實現部分。

1 / 12

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

複雜度分析

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