1. 程式人生 > 實用技巧 >[LeetCode] 32. Longest Valid Parentheses 最長有效括號

[LeetCode] 32. Longest Valid Parentheses 最長有效括號

Given a string containing just the characters'('and')', find the length of the longest valid (well-formed) parentheses substring.

Example 1:

Input: "(()"
Output: 2
Explanation: The longest valid parentheses substring is "()"

Example 2:

Input: ")()())"
Output: 4
Explanation: The longest valid parentheses substring is "()()"


分析:
結合題目“求最長有效括號子串長度”,是一個求最值的問題可以考慮嘗試使用動態規劃進行分析。使用動態規劃解題通常分為
定義狀態、狀態轉移方程、處理base case 三步。
第一步:定義狀態
  定義狀態陣列dp_a[N],可用dp_a[i] 表示字串 s[0:i]的最長有效括號子串長度,dp_a[i]根據s[i]的值,有不同的結果,
當 s[i]=='('時, dp_a[i] =dp_a[i-1],
當s[i] == ')'時,dp_a[i] = max(dp_a[i-1],dp_b[i]),其中dp_b[i]表示s[0:i]中,以s[i]結尾的最長有效括號子串長度。
經過以上分析,涉及兩個狀態,dp_a[i]表示s[0:i]的最長有效括號子串長度,dp_b[i]表示s[0:i]中,以s[i]結尾的最長有效括號子串長度。
dp_a[s.size()]是最終的結果。dp_a[i]的狀態轉移只和dp_a[i-1]和dp_b[i]有關,所以本題分析dp_b[i]的狀態轉移情況。

第二步:定義狀態轉移方程
由第一步狀態定義和狀態轉移的選擇分析,分析定義狀態方程:
1. 當 s[i] == '(' :
   dp_a[i] = dp_a[i-1]
dp_b[i] = 0;
2. 當 s[i] == ')':
  
dp_a[i] = max(dp_a[i-1],dp_b[i])
2.1 若 s[i-1] == '(': //"****()"
dp_b[i] = dp_b[i-2] +2 //"****()"
    2.2  若 s[i-1] == ')' //"****))"
若 dp_b[i-1]等於0,則,dp_b[i] = 0
若dp_b[i-1]大於0,則找到dp_b[i-1]對應子串的第一個字元的前一個字元的位置 pre_index,若pre_index >=0 ,且s[i] = '(',
       則 dp_b[i] = dp_b[i-1] + 2 + front,否則,dp_b[i] = 0;front表示dp_b[i]對應字串的首位字元的前一位的dp_b[]狀態,
       若 pre_index >= 0,fornt = dp_b[pre_index -1],否則 front = 0;
可見本題的難點就在 2.2中 狀態轉移方程的討論,因為dp_a[i]= max(dp_b[k],0<=k<=i),所以可以本題可以只求dp_b[i]表示的狀態,從中取得最大值,即可得到最終的結果

第三步:處理base case
由第二步分析得到的狀態轉移方程,求狀態dp_b[i],需要求dp[i-1]和dp[i-2],i 從2開始,所以base case 就是 i = 0 和i = 1的情況 。
dp_b[0] = 0;
dp_b[1] = s[0]=='('&&s[1]==')'?2:0;
本題的狀態轉移方向是從陣列s[]的最低位到最高位

程式碼如下: 時間複雜度 O(n),空間複雜度O(n),由於求dp[i]需要dp[i-1]和dp[i-2]兩個已知狀態,所以暫時還未想到將空間消耗壓縮到O(1)的方法。
 1 /*
 2  * @Descripttion: 
 3  * @version: 
 4  * @Author: wangxf
 5  * @Date: 2020-08-21 19:12:09
 6  * @LastEditors: Do not edit
 7  * @LastEditTime: 2020-10-26 01:13:54
 8  */
 9 /*
10  * @lc app=leetcode.cn id=32 lang=cpp
11  *
12  * [32] 最長有效括號
13  */
14 //[email protected] 
15 // @lc code=start
16 #include<stack>
17 using namespace std;
18 class Solution {
19 public:
20     int longestValidParentheses(string s) 
21     {
22         // dynamic planning
23         if(s.empty()||s.size()<2) return 0;
24         const int n =s.size();
25        //define status
26         int dp[n];
27         //base case 
28         dp[0] = 0;
29         dp[1]= (s[0]=='('&&s[1]==')'?2:0);
30         int res =dp[1];
31         //status move
32         for(int i=2;i<s.size();++i)
33         {
34             if(s[i]=='(') 
35             {
36                 dp[i] = 0;
37             }
38             else 
39             {
40                 if(s[i-1]=='(')//s[i-1]=='(',s[i] == ')'
41                 {
42                     dp[i] = dp[i-2]+2;
43                 }
44                 else //s[i-1]==')',s[i] == ')'
45                 {
46                          int pre_index = i-1-dp[i-1];
47                          dp[i]= dp[i-1]!=0&&pre_index >= 0 &&s[pre_index]=='('?dp[i-1]+2:0;
48                          if(pre_index-1>=0&&dp[i]>0)
49                          {
50                              dp[i] += dp[pre_index-1];
51                          }
52                 }
53             }
54             res = max(res,dp[i]);
55         }
56             return res;
57     }
58 };
59 // @lc code=end

方法二: 棧 + 雜湊

本題同樣可以使用 棧結合 雜湊的方法 解決。棧儲存左括號的陣列下標值。雜湊的key儲存匹配到的左右括號對的右括號陣列下標,value儲存對應的左括號的陣列下標值。
思路:用dp[i]表示s[0:i]中以s[i]結尾的最長有效括號子串長度。在遍歷s[n]的過程中,遇到‘(’將其對應的陣列下標值入棧,dp[i] = 0 。遇到‘)’則檢視棧是否為空,棧空則dp[i] = 0。
棧不空,取棧頂 j ,j就是當前‘)’對應匹配的‘(’的下標 ,dp[i] = i - j + 1 + dp[j-1],在雜湊中查詢 j-1,查詢不到 則 dp[j-1] = 0 ,查詢得到,則迴圈查詢,具體參考程式碼。

程式碼如下,時間複雜度:O(n^2) 空間複雜度 O(n),相比方法一,時間空間上都比較差,工程上,想不到方法一可以使用。

 1 /*
 2  * @Descripttion: 
 3  * @version: 
 4  * @Author: wangxf
 5  * @Date: 2020-08-21 19:12:09
 6  * @LastEditors: Do not edit
 7  * @LastEditTime: 2020-10-26 01:13:54
 8  */
 9 /*
10  * @lc app=leetcode.cn id=32 lang=cpp
11  *
12  * [32] 最長有效括號
13  */
14 //[email protected] 
15 // @lc code=start
16 #include<stack>
17 using namespace std;
18 class Solution {
19 public:
20     int longestValidParentheses(string s) 
21     {
22         if(s.empty()) return 0;
23         const int n =s.size();
24 //base case
25         int dp_a = 0;
26         stack<int> index_stack;//輔助棧
27         map<int,int> query_map;
28 //狀態轉移
29         int res = 0;
30         for(int i = 0;i<s.size();++i)
31         {
32             if(s[i]=='(')
33             {
34                 index_stack.push(i);
35                  dp_a = 0;
36             }
37             else //s[i]==')'
38             {
39                 if(index_stack.empty())
40                 {
41                     dp_a = 0;
42                 }
43                 else
44                 {
45                     int index = index_stack.top();
46                     int host_len = i - index + 1;
47                     int prefix_len = 0;
48                     while(query_map.find(index-1)!=query_map.end())
49                     {
50                         prefix_len+= ((index-1) - query_map[index-1] +1);
51                         index = query_map[index-1] ;
52                     }
53                     dp_a = host_len + prefix_len;
54                     query_map[i] = index;
55                     index_stack.pop();
56                 }
57             }
58              res  = max(res,dp_a); 
59         }
60         return res;
61     }
62 };
63 // @lc code=end