1. 程式人生 > >leetcode 第32題:動態規劃思想與堆疊的靈活使用

leetcode 第32題:動態規劃思想與堆疊的靈活使用

題目大意:

  給定一個只用’(‘和’)’組成的字串,輸入其最長合法成對子串的長度。

示例:

  輸入:(()),則應當輸出整數4;輸入:(()()),則應當輸出整數6;輸入:((())),應當輸出6。
 

解題思路:

  之所以一下跳了這麼多題,是因為之前發現自己在做動態規劃型別的題目方面比較薄弱,因此特地按型別搜尋題目,挑選了這一道題練手。
  這道題本身是hard級別的難度。首先,必須明確了暴力搜尋法肯定是行不通的。其次,在思考如何解決這一問題時,基於這是一道動態規劃的題目,因此我想套用一下前一道題的做法。先找出字串中的”()”子字串,然後將其左右擴充套件至最大,將最大長度保留。但這個方案仔細思考後發現也行不通。一是複雜度並不低,也有O(n^2),其次左右擴充套件時情況相對複雜,難以用程式表示。
  在參考了答案之後方才恍然大悟。解答中給出了兩種方案,一種是利用動態規劃的思想去解決,另一種則利用了堆疊的特性。
  第一種方案中,將字串從左至右依次遍歷,用陣列dp記錄遍歷到每一個字元處時,所在子串的當前最大合法長度。首先將dp全部置零。遍歷過程中,如果某處字元是’)’,並且前一個字元是’(‘,則此處的dp值應當為它向前數兩個位置的dp值加二;如果前一個字元是’)’並且當前字元對稱位置上的字元是’(‘,則說明成對,就應當在前一個字元的dp值的基礎上加2,同時還要把之前單獨成對的串的長度算進去。
  囉囉嗦嗦說了一堆,配合動畫演示更容易理解。具體可以見:

https://leetcode.com/problems/longest-valid-parentheses/solution/
  這套思路,明白之後很清晰,但要自己思考出來的確很難。在我看來,這套解法的關鍵,就是縷清了遍歷過程中合法字串的構成。有幾個關鍵的結構,一是”(()())”,二是”((()))”,三是它們的連線”(()())((()))”,弄清楚程式在遍歷這幾個字串過程中的行為就好理解多了。換個角度來說,就是如何以dp陣列為基礎建立動態規劃中的地推關係。這一過程,需要反覆實踐與思考才能得到解答。
  答案中還給出了使用堆疊解決的方案。這主要是利用了堆疊先進後出的特性。一般的想法當然是將符號入棧,這麼做存在一個問題:無法確定遍歷過程中的最長合法字串長度。答案給出的方法則是將字元對應的下標入棧。這樣,直接對棧內元素進行計算就容易得出結果。具體過程不再贅述,可以見如上的連結。
  用第一種方法AC的程式碼如下:
class Solution {
public:
int longestValidParentheses(string s) {
int len=s.length(),maxans=0;
vector<int> dp;
dp.push_back(0);
for(int i=1;i<len;i++){
if(s.at(i)=='(')
dp.push_back(0);
if(s.at(i)==')') //如果是右括號,則要記錄成對括號的數目
{
if(s.at(i-1)=='(') //如果是一重成對的括號,則在原來的基礎上加上2
{
dp.push_back((i>=2?dp[i-2]:0)+2);
}
else if((i>=dp[i-1]+1)&&s.at(i-dp[i-1]-1)=='(') //否則比較其對稱位置符號,如果是'(',則說明成對,則在原來的基礎上更新dp值
{
dp.push_back(dp[i-1]+((i>dp[i-1]+2)?dp[i-dp[i-1]-2]:0)+2);
}
else
dp.push_back(0);
maxans=max(maxans,dp[i]);
}
}
return maxans;
}
};