1. 程式人生 > >1089. Valid Parenthesis String

1089. Valid Parenthesis String

Given a string containing only three types of characters: '(', ')' and '*', write a function to check whether this string is valid. We define the validity of a string by these rules:

  1. Any left parenthesis '(' must have a corresponding right parenthesis ')'.
  2. Any right parenthesis ')' must have a corresponding left parenthesis '('
    .
  3. Left parenthesis '(' must go before the corresponding right parenthesis ')'.
  4. '*' could be treated as a single right parenthesis ')' or a single left parenthesis '(' or an empty string.
  5. An empty string is also valid.

Example 1:

Input: "()"
Output: True

Example 2:

Input: "(*)"
Output: True

Example 3:

Input: "(*))"
Output: True

Note:

  1. The string size will be in the range [1, 100].

解題思路:

這道題讓我們驗證括號字串,跟之前那道Valid Parentheses有些類似。不同之處在於這道題只有小括號,不過還存在星號,星號可以當左括號,右括號,或空來使用,問我們能不能得到一個合法的括號字串。

那麼我們想,如果不存在星號,那麼這題是不是異常的簡單,我們甚至連stack都可以不用,直接用一個變數,遇到左括號,自增1,遇到右括號,如果變數為0,直接返回false,否則自減1,最後只要看變數是否為0即可。

但是由於星號的存在,這道題難度就變的複雜了,由於星號可以當括號用,所以當遇到右括號時,就算此時變數為0,也可以用星號來當左括號使用。那麼星號什麼時候都能當括號來用嗎,我們來看兩個例子 *) 和 *( ,在第一種情況下,星號可以當左括號來用,而在第二種情況下,無論星號當左括號,右括號,還是空,*( 都是不對的。當然這種情況只限於星號和左括號之間的位置關係,而只要星號在右括號前面,就一定可以消掉右括號。

那麼我們使用兩個stack,分別存放左括號和星號的位置,遍歷字串,當遇到星號時,壓入星號棧star,當遇到左括號時,壓入左括號棧left,當遇到右括號時,此時如果left和star均為空時,直接返回false;如果left不為空,則pop一個左括號來抵消當前的右括號;否則從star中取出一個星號當作左括號來抵消右括號。

當迴圈結束後,我們希望left中沒有多餘的左括號,就算有,我們可以嘗試著用星號來抵消,當star和left均不為空時,進行迴圈,如果left的棧頂左括號的位置在star的棧頂星號的右邊,那麼就組成了 *( 模式,直接返回false;否則就說明星號可以抵消左括號,各自pop一個元素。最終退出迴圈後我們看left中是否還有多餘的左括號,沒有就返回true,否則false,參見程式碼如下:

解法一:

    def checkValidString(self, s):
        # Write your code here
        left, star = [], []
        for i in range(len(s)):
            if s[i] == '*':
                star.append(i)
            elif s[i] == '(':
                left.append(i)
            else:
                if len(star) == 0 and len(left) == 0:
                    return False
                if len(left) != 0:
                    left.pop()
                else:
                    star.pop()
        
        
        while left and star:
            if left[-1] > star[-1]:
                return False
            left.pop()
            star.pop()
        
        return len(left) == 0

下面這種方法是用遞迴來寫的,思路特別的簡單直接,感覺應該屬於暴力破解法。

使用了變數cnt來記錄左括號的個數,變數start表示當前開始遍歷的位置,那麼在遞迴函式中,首先判斷如果cnt小於0,直接返回false。否則進行從start開始的遍歷,如果當前字元為左括號,cnt自增1;如果為右括號,若cnt此時小於等於0,返回false,否則cnt自減1;如果為星號,我們同時遞迴三種情況,分別是當星號為空,左括號,或右括號,只要有一種情況返回true,那麼就是true了。如果迴圈退出後,若cnt為0,返回true,否則false,參見程式碼如下:

解法二:TLE

    def checkValidString(self, s):
        # Write your code here
        def helper(s, start, cnt):
            if cnt < 0:
                return False
            
            for i in range(start, len(s)):
                if s[i] == '(':
                    cnt += 1
                elif s[i] == ')':
                    if cnt <= 0:
                        return False
                    cnt -= 1
                else:
                    return helper(s, i+1, cnt) or helper(s, i+1, cnt+1) or helper(s, i+1, cnt-1)
            
            return cnt == 0
        
        return helper(s, 0, 0)

下面這種解法是論壇上排第一的解法,感覺思路清新脫俗,博主研究了好久,參考了網友的留言才稍稍弄懂了一些,

這裡維護了兩個變數low和high,其中low表示在有左括號的情況下,將星號當作右括號時左括號的個數(這樣做的原因是儘量不多增加右括號的個數),而high表示將星號當作左括號時左括號的個數。是不是很繞,沒辦法。

那麼當high小於0時,說明就算把星號都當作左括號了,還是不夠抵消右括號,返回false。

而當low大於0時,說明左括號的個數太多了,沒有足夠多的右括號來抵消,返回false。

那麼開始遍歷字串,當遇到左括號時,low和high都自增1;

當遇到右括號時,只有當low大於0時,low才自減1,保證了low不會小於0,而high直接自減1;

當遇到星號時,只有當low大於0時,low才自減1,保證了low不會小於0,而high直接自增1,因為high把星號當作左括號。當此時high小於0,說明右括號太多,返回false。當迴圈退出後,我們看low是否為0,參見程式碼如下:

解法三:

class Solution:
    """
    @param s: the given string
    @return: whether this string is valid
    """

    #
    def checkValidString(self, s):
        # Write your code here
        unpair_left = 0
        avail_left = 0
        for ch in s:
            if ch == "(":
                unpair_left += 1
                avail_left += 1

            elif ch == "*":
                if unpair_left > 0:
                    unpair_left -= 1
                avail_left += 1

            else:
                if unpair_left > 0:
                    unpair_left -= 1
                avail_left -= 1
                
                if avail_left < 0:
                    return False

        return unpair_left == 0