▶ 在一串 '(' 和 ')' 组成的字符串中寻找最长的匹配的子串,如 ()),(()),()(),()(()) 等都算合法。
● 代码,暴力搜索,超时。使用两个变量枚举原字符串的 O(n2) 个子串,每个子串用 O(n) 的时间去验证是否匹配,时间复杂度 O(n3),以下为稍微改进的版本,没有本质变化。
1 class Solution 2 { 3 public: 4 bool check(string & ss, int start, int end) 5 { 6 int i, count; 7 for (i = start, count = 0; i <= end; i++) 8 { 9 ss[i] == '(' ? count++ : count--; 10 if (count < 0) 11 return false; 12 } 13 return (count == 0); 14 } 15 int longestValidParentheses(string s) 16 { 17 if (s.size() <= 1) 18 return 0; 19 20 int i, j, maxLength; 21 for (i = 1, maxLength = 0; i < s.size(); i++) 22 { 23 if (s[i] == '(') 24 continue; 25 for (j = 0; j < i - maxLength; j++)// 每找到一个 ') '就从 s 开头开始寻找最长的合法串, 26 { 27 if (check(s, j, i) && i - j + 1 > maxLength)// 找到的合法串比之前的更长,则更新 maxLength 28 maxLength = i - j + 1; 29 } 30 } 31 return maxLength; 32 } 33 };
● 代码,愚蠢的动态规划,326 ms 。想到了用动态规划但是没有用对方法,使用数组 maxLength 来保存原字符串各前缀的最大匹配长度,还需要维护另外两个数组 maxStart 和 maxAtEnd(两者等价,不同情况下可使表达式稍微简化)来记录相关信息,且三个数组间相互关联不明显。
1 class Solution 2 { 3 public: 4 bool check(string & ss, int start, int end)// 检查 s 中下标从 start 到 end(两边都取得到)是否匹配 5 { 6 if (end < start || !((end - start) % 2))// 起点在终点右边,或者包含奇数个字符,肯定不匹配 7 return false; 8 int i, count; 9 for (i = start, count = 0; i <= end; i++) 10 { 11 ss[i] == '(' ? count++ : count--; 12 if (count < 0) 13 return false; 14 } 15 return (count == 0); 16 } 17 int longestValidParentheses(string s) 18 { 19 if (s.size() <= 1) 20 return 0; 21 int i, j, temp; 22 vector<int> maxLength(s.size(), 0); // maxLength[i] 记录 s[0] ~ s[i] 最大匹配长度 23 vector<int> maxStart(s.size(), 0); // maxStart[i] 记录 s[0] ~ s[i] 最大匹配长度的起点 24 vector<bool> maxAtEnd(s.size(), false); // maxAtEnd[i] = (maxStart[i] + maxLength[i] -1 == i),即最大匹配长度是否位于末尾 25 26 for (i = 1; i < s.size(); i++)// 填表,每次循环在末尾添加一个字符,研究匹配情况 27 { 28 maxLength[i] = maxLength[i - 1]; 29 maxStart[i] = maxStart[i - 1]; 30 if (s[i] == '(') 31 continue; 32 if (maxAtEnd[i - 1])// 长为 i 的前缀(下标为 i - 1)中最长匹配位于末尾 33 { 34 if (maxStart[i - 1] > 0 && s[maxStart[i - 1] - 1] == '(')// 检查该匹配前一个字符是否为 '(',否则不能成为更长的匹配 35 { 36 for (j = maxStart[i - 1] - 2 - maxLength[maxStart[i - 1] - 2]; 37 j < maxStart[i - 1] - 2 && !check(s, j, maxStart[i - 1] - 2); j++); 38 // 以上面找到的 '(' 左侧的字符为起点, 继续往前寻找匹配。 39 // 不一定是该范围内的最长匹配,但长度不超过该范围的最大匹配长度 maxLength[maxStart[i - 1] - 2] 40 if (j < maxStart[i - 1] - 2)// 找到了新的最长匹配,更新数据 41 { 42 maxLength[i] = i - j + 1; 43 maxStart[i] = j; 44 } 45 else// 没有找到新的最长匹配,仅在原来的基础上扩展一对括号 46 { 47 maxLength[i] += 2; 48 maxStart[i] -= 1; 49 } 50 maxAtEnd[i] = true; 51 } 52 } 53 else if (check(s, temp = maxStart[i - 1] + maxLength[i - 1], i))// 恰能在原匹配的后面接上一个匹配 54 { 55 maxLength[i] += i - temp + 1; 56 maxAtEnd[i] = true; 57 } 58 else // 完全不能与原匹配产生联系,在后半段逐个查找 59 { 60 for (j = temp + 1; j < i && i - j + 1 >= maxLength[i]; j++) 61 { 62 if (check(s, j, i)) 63 { 64 maxLength[i] = i - j + 1; 65 maxStart[i] = j; 66 maxAtEnd[i] = true; 67 } 68 } 69 } 70 } 71 return maxLength[s.size() - 1]; 72 } 73 };
● 代码,正确的动态规划,10 ms 。数组 dp[ i ] 记录的是 “以 s[ i ] 为结尾的子串的最大匹配长度”,可以根据 s[ i - 1 ] 的值简单的分为两种情况。
1 class Solution 2 { 3 public: 4 int longestValidParentheses(string s) 5 { 6 int output = 0; 7 vector<int> dp(s.size()); 8 for (int i = 1; i < s.length(); i++) 9 { 10 if (s[i] == ')') 11 { 12 if (s[i - 1] == '(') // "...(...√...)()" 型,使用 dp[i-2] 来计算 13 (i >= 2) ? (dp[i] = dp[i - 2] + 2) : (dp[i] = 2); 14 else if (i - dp[i - 1] > 0 && s[i - dp[i - 1] - 1] == '(')// "(...√...)((...√...))" 型,要计算 dp[i-1] 以及更前面的那个 15 (i - dp[i - 1] >= 2) ? (dp[i] = dp[i - 1] + dp[i - dp[i - 1] - 2] + 2) : (dp[i] = dp[i - 1] + 2); 16 output = (dp[i] > output) ? dp[i] : output; 17 } 18 } 19 return output; 20 } 21 };
● 代码,栈法,13 ms 。
1 class Solution 2 { 3 public: 4 int longestValidParentheses(string s) 5 { 6 int output = 0; 7 stack<int> st; 8 st.push(-1); 9 for (int i = 0; i < s.length(); i++) 10 { 11 if (s[i] == '(') 12 st.push(i); 13 else // 遇 ')' 出栈并计算 14 { 15 st.pop(); 16 if (st.empty()) 17 st.push(i); 18 else 19 output = (i - st.top() > output) ? i - st.top() : output; 20 } 21 } 22 return output; 23 } 24 };
● 代码,神奇的左右扫描法,13 ms 。两个方向扫描数组,计算最大匹配长度作为输出。
1 class Solution 2 { 3 public: 4 int longestValidParentheses(string s) 5 { 6 int left = 0, right = 0, maxLength = 0; 7 for (int i = 0; i < s.length(); i++) // 从左往右扫描,相等时计算最大匹配长度,')' 不少于 '(' 时计数归零 8 { 9 (s[i] == '(') ? left++ : right++; 10 if (left == right) 11 maxLength = (2 * right > maxLength) ? 2 * right : maxLength; 12 else if (right >= left) 13 left = right = 0; 14 } 15 left = right = 0; 16 for (int i = s.length() - 1; i >= 0; i--) // 从右往左扫描,相等时计算最大匹配长度,'(' 不少于 ')' 时计数归零 17 { 18 (s[i] == '(') ? left++ : right++; 19 if (left == right) 20 maxLength = (2 * left > maxLength) ? 2 * left : maxLength; 21 else if (left >= right) 22 left = right = 0; 23 } 24 return maxLength; 25 } 26 };