一、概述
輸入一個只包含左括號和右括號的字符串,輸出其最長合法序列的長度。
什麼叫最長合法序列呢?就是在此序列中,不改變順序,每一個左括號的後面總有一個右括號與之對應。例如
((()))
()()()
但是
))((
這種不是。
二、分析
1、我的思路
最開始我的思路類似之前寫Valid Parentheses一樣,用棧保存左括號,有一個有括號就出棧一個。但是問題來了:
如果只有一個合法子序列,那麼:
左括號剩餘,那右括號數量*2就是合法序列長度;右括號剩餘,那麼左括號*2就是合法序列長度。
但是如果不止一個合法子序列呢?
我們該如何延長一個合法子序列的長度呢?
我想到這樣一種方法:
已經有一對(),現在又出現了(,往後如果這個()能作爲子序列頭,則這個)後面的左括號壓入一個新棧,然後彈出,直到棧空,這樣就可以延長這一子序列的長度。
但是太麻煩了。有好多子序列得多少棧啊。邊界條件也難以界定。
於是我選擇了一個十分直觀的方法:
多次遍歷輸入序列,每次遇到(),把它們變成**,如果遇到*,那麼接着往下,無視它。
直到最後所有的()都變成**。
然後找到最長的*子序列,其長度就是結果。思路很簡單,因此實現也很簡單。代碼如下:
class Solution {
public:
int longestValidParentheses(string s) {
int len=s.size();
if(len<=1)
return 0;
int num=-1;
int now=0;
int first=-1;
while(num<now)
{
num=now;
first=-1;
for(int i=0;i<len;++i)
{
if(s[i]=='(')
{
first=i;
}
else if(first!=-1&&s[i]==')')
{
s[first]='*';
first=-1;
s[i]='*';
now=now+2;
}
else
continue;
}
}
int sum=0;
int max=0;
for(int i=0;i<len;++i)
{
if(s[i]=='*')
{
++sum;
}
else
{
if(max<sum)
max=sum;
sum=0;
}
}
if(max<sum)
return sum;
return max;
}
};
有幾個地方要注意,每次進入循環的時候,要先更新當前的最大長度,重置first。
別看時空複雜度好像很不錯,但是我這是重複提交四次選最好的,這玩意太不準了:
2、較好的算法
我想不出,只是在底下喊666的份兒。但是看還是能看明白的:
class Solution {
public:
int longestValidParentheses(string s) {
stack<int> stk;
stk.push(-1);
int maxL=0;
for(int i=0;i<s.size();i++)
{
int t=stk.top();
if(t!=-1&&s[i]==')'&&s[t]=='(')
{
stk.pop();
maxL=max(maxL,i-stk.top());
}
else
stk.push(i);
}
return maxL;
}
};
他用了一個棧,並且只循環了一次。這個棧中保存什麼呢?保存的元素可以按如下形式描述:
如果循環在當前中斷,棧中元素都是沒能匹配的廢物元素,是一羣單身狗。
舉例子更恰當:
序列爲) ( ) ( ) ) ( ) ( ) (
首先 ) 入棧;然後 ( 入棧;然後判斷可知 ) 可以和棧頂的 ( 匹配。如果到這裏循環中斷,第一個 ) 就是單身狗。
但是沒中斷,因此目前棧頂的 ( 彈出;每次執行彈出操作後都會更新子序列長度的最大值。
這也就是該算法的核心:
子序列如何劃分?什麼樣的元素能夠把子序列劃分開?
答案就是廢物元素。也就是,當前的棧頂元素就是當前子序列的開頭,當前循環到的元素就是當前子序列的末尾。這很重要,可能有點難理解。
然後 ( 入棧。。。。。。
最後棧裏面剩什麼呢?剩下 ) ) ( 正好是三個廢物。它們在主序列中劃分出兩個子序列,長度都是4。因此結果爲4。
我覺得,能夠想出這個思路,要對問題有一個清晰的認識:
找最長合法子序列->子序列是什麼?->如何劃分子序列?->歸納推理,保存子序列的起點,動態更新終點,也就動態更新了長度。
自嘆弗如。
三、總結
多觀察,多思考,多做題。沒別的了。