動態規劃的重點推導出狀態轉移方程
題目描述
先來看下這個題目描述:
給定一個只包含 ‘(’ 和 ‘)’ 的字符串,找出最長的包含有效括號的子串的長度。
示例
輸入: “)()())”
輸出: 4
解釋: 最長有效括號子串爲 “()()”
參閱官方題解中「方法2-動態規劃」
題解-初步理解
- 初始化dp數組,dp[0]*len(s)
- dp[i]表示以該位元素結尾的最長有效子字符串的長度
- 在遍歷字符串的時候,只要該位是左括號,就保持dp[i] = 0
- 從後向前推導dp之間的狀態轉移方程,假設我們現在位於i == 5
- 更新i = 4的位置爲2
- 接下來要判斷位是有效子括號對前面那位,即i = 2,發現這也是一對有效子括號
- 那麼與5對應的左括號一定不在這個有效對中咯,繼續向前尋找,發現dp[0]=0,所以最終有效括號即爲這兩對dp[2]+dp[4]=4
這個示例沒有找到前面所對應的與最後一位匹配的左括號,但是可以幫助我們先理解狀態數組,以及如何尋找與i對應的位置,即減去內部的有效子串dp[i-1],我們再看一個示例:
題解-更進一步
“()((())”
- 初始化這個dp數組,先填入不「鑲嵌」的有效子括號對。
- 現在我們來觀察i = 3,已知基礎長度爲2,更新dp[6]爲2
- 在上一狀態中,我們知道i = 3和i =6中還夾了一對有效子串,並存儲在i = 5中,因此
dp[i] = 2 + dp[i-1],因此dp[5]更新爲4.
- i = 2時爲0,因此i = 6保持不變,依然爲4。
題解-最終推導
現在來看當查找外部時爲右括號的情況
“()(())”
- 狀態轉移方程變成
dp[i] = 2 + dp[i-1] + dp[i-dp[i-1]-2] - 我們用這個方程求解一下dp[1]
dp[1] = 2 + dp[0] + dp[1-dp[0]-2] = 2 + 0 + dp[-1]
dp[-1]不存在,因此爲0,最後dp[1] = 2 - 你以爲結束了麼?No,這裏存在一個問題,就是在python中dp[-2]是倒數第2個元素,本例中dp[-2] = 2
- 所以我們要加入一個限制條件,即被查找元素的值一定要大於等於0
代碼實現
class Solution2:
"""動態規劃 i-dp[i-1]-1是與當前)對稱的位置"""
def longestValidParentheses(self, s: str) -> int:
n = len(s)
if n == 0:
return 0
dp = [0]*n
for i in range(len(s)):
if s[i] == ')' and i-dp[i-1]-1 >= 0 and s[i-dp[i-1]-1]=='(':
dp[i] = 2 + dp[i-1] + dp[i-dp[i-1]-2]
return max(dp)
另一種神奇而被人喜歡的方法
雖然動態規劃很厲害,但我個人還是很喜歡方法4…
class Solution4:
"""正向逆向相結合的方法
當右括號個數 > 左括號個數的時候,直接將"左括號個數"、"右括號個數"重新初始化爲0
逆向遍歷時,當右括號 < 左括號個數,直接將"左括號個數"、"右括號個數"重新初始化爲0
時間複雜度O(N),空間複雜度O(1)
"""
def longestValidParentheses(self, s: str) -> int:
n, left, right, maxlength = len(s), 0, 0, 0
# 正序遍歷
for i in range(n):
if s[i] == '(':
left += 1
else:
right += 1
if left == right:
maxlength = max(maxlength,2*right)
elif right > left:
left = right = 0
# 反序遍歷
left = right = 0
for i in range(n-1,-1,-1):
if s[i] == '(':
left += 1
else:
right += 1
if left == right:
maxlength = max(maxlength,2*left)
elif right < left:
left = right = 0
return maxlength
感覺就是思考能簡單則簡單哈哈