题记
这一篇是Leetcode动态规划简单题之后的又一个关于动态规划的博客,主要按照leetcode的tag里面写的,可能会不定期更新吧
题目
32 最长有效括号
给定一个只包含 ‘(’ 和 ‘)’ 的字符串,找出最长的包含有效括号的子串的长度。
输入: “(()”
输出: 2
解释: 最长有效括号子串为 “()”
注意. “()(())“有效长度是6。
题解
主要是情况有点多,慢慢想都能解出来。
定义状态dp[i]以第i个括号结尾的符号中有效括号串的长度。
第一个大类别是以’(‘均不能组成有效括号串,所以dp为0.
第二个大类有2种情况(粗体字表示s[i-1]):1. ()() 2. a. ()()))  b. ()(())) c. ()((()))
第一种情况是dp[i]=dp[i-2]+2,第二种情况(a和c可以合并成一种情况,不过我就按照我第一次写这道题的思路写了):
a. 如果dp[i-1]==0, dp[i]=0
b. 如果dp[i-1]!=0, 前面(指s[i- dp[i-1] -1])有可以组成括号的,则dp[i]=dp[i-1]+2
c. 如果dp[i-1]!=0, 前面没有可以组成括号的,则dp[i]=0
|  1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
 | class Solution:
    def longestValidParentheses(self, s: str) -> int:
        if len(s)<2:
            return 0
        result = 0
        dp = [0] * len(s)
        if s[:2] == '()':
            dp[1] = 2
            result = 2
        for i in range(2, len(s)):
            if s[i] == '(': # 第一个大类别
                continue
            # 第二个大类别 s[i] == ')'
            if s[i-1] == '(':
                dp[i] = dp[i-2] + 2
                result = max(result, dp[i])
            else:
                if dp[i-1] == 0:
                    dp[i] = 0
                else:
                    if i-dp[i-1]-1 >= 0 and s[i-dp[i-1]-1] == '(':
                        dp[i] = dp[i-1] + 2
                        if i-dp[i-1]-2 >= 0: #有个python答案里面不做这个判决,因为边界dp[-1]为0
                            dp[i] += dp[i-dp[i-1]-2]
                        result = max(result, dp[i])
                    else:
                        dp[i] = 0
        return result
 | 
 
72 编辑距离

p.s. 编辑距离算法被数据科学家广泛应用,是用作机器翻译和语音识别评价标准的基本算法。
题解
这个题第一眼看的毫无头绪,直接看别人的题解了。
参考资料:编辑距离面试题详解


|  1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
 | class Solution:
    def minDistance(self, word1: str, word2: str) -> int:
        m,n = len(word1), len(word2)
        dp = [[0]*(n+1) for _ in range(m+1)]
        for i in range(m):
            dp[i+1][0] = i+1
        for j in range(n):
            dp[0][j+1] = j+1
        for i in range(m):
            for j in range(n):
                if word1[i] == word2[j]:
                    dp[i+1][j+1] = dp[i][j]
                else:
                    dp[i+1][j+1] = min(dp[i+1][j], dp[i][j+1], dp[i][j]) + 1
        return dp[m][n]
 | 
 
85 最大矩形

120 三角形最小路径和

题解
我最开始想到的状态转移方程:dp[i][j] = tri[i][j] + min( dp[i-1][j] , dp[i-1][j] ),不过这个是从下往上找到最小路径和,所以结果是tri[m-1][0] ,tri[i][j]实际上是tri[-i-1][j]。
| 1 dp[2][0] triangle[-3][0] |  |  | 
| 2 dp[1][0] triangle[-2][0] | 3 dp[1][1] triangle[-2][1] |  | 
| 4 dp[0][0] triangle[-1][0] | 5 dp[0][1] triangle[-1][1] | 6 dp[0][2] triangle[-1][2] | 
|  1
 2
 3
 4
 5
 6
 7
 8
 9
10
 | class Solution:
    def minimumTotal(self, triangle: List[List[int]]) -> int:
        m,n = len(triangle), len(triangle[-1]) # m行n列
        dp = [[0]*n for _ in range(m)]
        for i in range(n):
            dp[0][i] = triangle[-1][i]
        for i in range(1,m):
            for j in range(len(triangle[-i-1])):
                dp[i][j] = triangle[-i-1][j] + min(dp[i-1][j], dp[i-1][j+1])
        return dp[m-1][0]
 | 
 
上面代码的空间复杂度是O(m*n),由于dp只依赖于相邻行,所以很容易优化成O(n),n是底层列数。(实际上行数和列数相等,即m=n,所以也可以说O(m))
|  1
 2
 3
 4
 5
 6
 7
 8
 9
10
 | class Solution:
    def minimumTotal(self, triangle: List[List[int]]) -> int:
        n = len(triangle)
        dp =[0]*n
        for i in range(n):
            dp[i] = triangle[-1][i]
        for i in range(1,n):
            for j in range(len(triangle[-i-1])):
                dp[j] = triangle[-i-1][j] + min(dp[j], dp[j+1])
        return dp[0]
 | 
 
139 单词拆分
给定一个非空字符串 s 和一个包含非空单词的列表 wordDict,判定 s 是否可以被空格拆分为一个或多个在字典中出现的单词。
先给我自己的思路,dp[i]表示以第i个字母结尾的子字符串是否能够匹配上。
状态转移方程为:dp[i] = dp[i-匹配的单词长度] && 第i-匹配的单词长度+1 到 第i个字母 所组成的单词在字典里,这里处理一下边界i-word_size。
例如,假设字典为:is、son、song:
| i | s | s | o | n | g | i | s | 
| False | True | False | False | True | True | False | True | 
|  1
 2
 3
 4
 5
 6
 7
 8
 9
10
 | class Solution:
    def wordBreak(self, s: str, wordDict: List[str]) -> bool:
        size = len(s)
        dp = [False] * size
        for i in range(size):
            for word in wordDict:
                word_size = len(word)
                if s[i-word_size+1: i+1] == word and (dp[i-word_size] is True or i-word_size<0):
                    dp[i] = True
        return dp[-1]
 | 
 
152 乘积最大子数组
给你一个整数数组 nums ,请你找出数组中乘积最大的连续子数组(该子数组中至少包含一个数字),并返回该子数组所对应的乘积。
输入: [-2,0,-1]
输出: 0
解释: 结果不能为 2, 因为 [-2,-1] 不是子数组。
dp1为前i个子数组所得到的最大非负数乘积,dp2为前i个子数组所得到的最大非正数乘积。
重点是做一个分类讨论,nums[i]为负数和正数的情况。
|  1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
 | class Solution:
    def maxProduct(self, nums: List[int]) -> int:
        res = nums[0]
        dp1 = max(0, nums[0])
        dp2 = min(0, nums[0])
        for i in range(1, len(nums)):
            if nums[i] == 0:
                dp1,dp2 = 0,0
            elif nums[i] > 0:
                dp1 = max(dp1*nums[i], nums[i]) # dp1可能为0,若为0取nums[i]
                dp2 *= nums[i] # 正数乘正数
            else:
                tmp = dp1
                dp1 = dp2 * nums[i]
                dp2 = min(tmp * nums[i], nums[i])
            # print(dp1,dp2)
            res = max(res, dp1)
        return res
 |