总结
大纲
相关细节
如果题目求最大值,则dp初始化为[-float(“inf”)]*n的数据
循环比较一般使用dp[i] = max(dp[i], dp[j] + nums[i]),而不是dp[i] = max(dp[i], dp[j]) + nums[i]
序列
单序列
模板
一般初始化的时候,都会dp = [0] * len(str),而不是dp = [0] * (len(str)+1)
注意有的时候不是输出dp[n-1]而是max(dp)
其他点
最长递增子序列[300]
位于 https://leetcode.cn/problems/longest-increasing-subsequence
dp表达式如下
1 if (nums[i] > nums[j]) dp[i] = max(dp[i], dp[j] + 1);
代码如下
1 2 3 4 5 6 7 8 class Solution (object ): def lengthOfLIS (self, nums ): dp = [1 ] * len (nums) for i in range (1 , len (nums)): for j in range (i): if nums[i] > nums[j]: dp[i] = max (dp[i], dp[j] + 1 ) return max (dp)
最长连续递增序列[674]
位于 https://leetcode.cn/problems/longest-continuous-increasing-subsequence
dp表达式如下
1 if (nums[i] > nums[i-1]) dp[i] = max(dp[i], dp[i-1] + 1);
代码如下
1 2 3 4 5 6 7 class Solution (object ): def findLengthOfLCIS (self, nums ): dp = [1 ] * len (nums) for i in range (1 , len (nums)): if nums[i] > nums[i-1 ]: dp[i] = max (dp[i], dp[i-1 ] + 1 ) return max (dp)
最大子数组和[53]
位于 https://leetcode.cn/problems/maximum-subarray
1 2 3 4 5 6 7 class Solution (object ): def maxSubArray (self, nums ): dp = [0 ] * len (nums) dp[0 ] = nums[0 ] for i in range (1 , len (nums)): dp[i] = max (dp[i-1 ]+nums[i], nums[i]) return max (dp)
回文子串[647]
位于 https://leetcode.cn/problems/palindromic-substrings/description/
动规表达式如下
1 2 3 dp[i][i] = 1 if s[i]==s[j] and j-i==1: dp[i][j]=1 if j-i>1 and dp[i+1][j-1] and s[i]==s[j]:dp[i][j]=1
题解如下
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 class Solution (object ): def countSubstrings (self, s ): n = len (s) dp = [[0 ]*n for _ in range (n)] cnt = 0 for i in range (n): dp[i][i] = 1 cnt += 1 for j in range (n): for i in range (j): if j-i==1 and s[i]==s[j]: dp[i][j] = 1 cnt += 1 elif j -i > 1 and dp[i+1 ][j-1 ] and s[i]==s[j]: dp[i][j] = 1 cnt += 1 return cnt
遍历顺序是这样的
最长回文子串[5]
位于 https://leetcode.cn/problems/longest-palindromic-substring
子串是连续的,子序列不是,这里要注意,和上面解法一样,代码没变
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 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 class Solution (object ): def longestPalindrome (self, s ): n = len (s) dp = [[0 ]*n for _ in range (n)] m = 0 res = None for i in range (n): dp[i][i] = 1 if i-i+1 >m: m = i-i+1 res = [i,i] for j in range (n): for i in range (j): if j-i==1 and s[i]==s[j]: dp[i][j] = 1 if j-i+1 >m: m = j-i+1 res = [i,j] elif j-i>1 and s[i]==s[j] and dp[i+1 ][j-1 ]==1 : dp[i][j] = 1 if j-i+1 >m: m = j-i+1 res = [i,j] return s[res[0 ]:res[1 ]+1 ] class Solution : def longestPalindrome (self, s: str ) -> str : n = len (s) dp = [[False ] * n for _ in range (n)] max_flag = 0 ind = 0 for i in range (n): dp[i][i] = True ind = (i, i) for l in range (1 , n): for i in range (n - l): j = i + l if j-i==1 and s[i]==s[j]: dp[i][j] = True if j - i > max_flag: max_flag = j - i ind = (i, j) if j-i>1 and s[i] == s[j]: dp[i][j] = dp[i + 1 ][j - 1 ] if dp[i][j] and j - i > max_flag: max_flag = j - i ind = (i, j) return (s[ind[0 ]:ind[1 ] + 1 ])
环形子数组的最大和[918]
位于 https://leetcode.cn/problems/maximum-sum-circular-subarray/description/
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 class Solution : def maxSubarraySumCircular (self, nums: List [int ] ) -> int : n = len (nums) max_ = -float ("inf" ) dp_max = [0 ] * n for i in range (n): dp_max[i] = max (0 , dp_max[i-1 ]) + nums[i] if dp_max[i] > max_: max_ = dp_max[i] if max (dp_max) < 0 : return max (nums) min_ = float ("inf" ) dp_min = [0 ] * n for i in range (n): dp_min[i] = min (0 , dp_min[i-1 ]) + nums[i] if dp_min[i] < min_: min_ = dp_min[i] return max (max_, sum (nums) - min_)
最长回文子序列[516]
位于 https://leetcode.cn/problems/longest-palindromic-subsequence
1 2 3 4 5 6 7 8 9 10 11 12 13 14 class Solution : def longestPalindromeSubseq (self, s: str ) -> int : dp = [[0 ] * len (s) for _ in range (len (s))] for i in range (len (s)): dp[i][i] = 1 n = len (s) for l in range (1 , n): for i in range (n - l): j = i + l if s[i] == s[j]: dp[i][j] = dp[i + 1 ][j - 1 ] + 2 else : dp[i][j] = max (dp[i + 1 ][j], dp[i][j - 1 ]) return dp[0 ][n-1 ]
戳气球[312]
位于 https://leetcode.cn/problems/burst-balloons
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 31 32 33 34 35 class Solution : def maxCoins (self, nums: List [int ] ) -> int : n = len (nums) val = [1 ] + nums + [1 ] @lru_cache(None ) def solve (left: int , right: int ) -> int : if left >= right - 1 : return 0 best = 0 for i in range (left + 1 , right): total = val[left] * val[i] * val[right] total += solve(left, i) + solve(i, right) best = max (best, total) return best return solve(0 , n + 1 ) class Solution : def maxCoins (self, nums: List [int ] ) -> int : n = len (nums) rec = [[0 ] * (n + 2 ) for _ in range (n + 2 )] val = [1 ] + nums + [1 ] for i in range (n - 1 , -1 , -1 ): for j in range (i + 2 , n + 2 ): for k in range (i + 1 , j): total = val[i] * val[k] * val[j] total += rec[i][k] + rec[k][j] rec[i][j] = max (rec[i][j], total) return rec[0 ][n + 1 ]
https://leetcode.cn/problems/burst-balloons/solutions/337630/zhe-ge-cai-pu-zi-ji-zai-jia-ye-neng-zuo-guan-jian-/
鸡蛋掉落[887]
位于 https://leetcode.cn/problems/super-egg-drop 本题需要反过来想,如果我们可以做 t 次操作,而且有 k 个鸡蛋,那么我们能找到答案的最高的 n 是多少
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 class Solution : def superEggDrop (self, k: int , n: int ) -> int : if n==1 : return 1 dp = [[0 ]*(k+1 ) for _ in range (n+1 )] for i in range (1 ,k+1 ): dp[1 ][i] = 1 ans = -1 for i in range (2 ,n+1 ): for j in range (1 ,k+1 ): dp[i][j] = 1 + dp[i-1 ][j-1 ] + dp[i-1 ][j] if dp[i][k] >=n: ans = i break return ans
建议还是记下来吧,不然很容易忘
双序列
模板
初始化dp为dp=[[0]*(n+1) for _ in range(m+1)],要多一位
在判断的时候要少一位,比如num[i-1]就是对应到dp[i]这里来
初始化一般都是0
注意判断好条件
简单代码如下:
1 2 3 4 5 6 7 8 9 10 n1 = len (text1) n2 = len (text2) dp = [[0 ]*(n2+1 ) for _ in range (n1+1 )] result = -1 for i in range (1 , n1+1 ): for j in range (1 , n2+1 ): if text1[i-1 ] == text2[j-1 ]: dp[i][j] = func(dp[i][j], dp[i-1 ][j-1 ] + 1 ) else : dp[i][j] = func(..)
最长重复子数组[718]
位于 https://leetcode.cn/problems/maximum-length-of-repeated-subarray
注意题目中说的子数组,其实就是连续子序列。和最长连续递增序列[674]有点类似,下面的最长公共子序列和最长递增子序列[300]有点像。
dp表达式如下
1 A[i - 1] 和B[j - 1]相等的时候,dp[i][j] = dp[i - 1][j - 1] + 1;;
代码如下
1 2 3 4 5 6 7 8 9 10 11 12 class Solution (object ): def findLength (self, nums1, nums2 ): n1 = len (nums1) n2 = len (nums2) dp = [[0 ]*(n2+1 ) for _ in range (n1+1 )] max_res = -1 for i in range (1 , n1+1 ): for j in range (1 , n2+1 ): if nums1[i-1 ] == nums2[j-1 ]: dp[i][j] = max (dp[i][j], dp[i-1 ][j-1 ] + 1 ) max_res = max (max_res, dp[i][j]) return max_res
最长公共子序列[1143]
位于 https://leetcode.cn/problems/longest-common-subsequence
和 最长重复子数组[718] 不一样,这道题不需要连续的数组。
p表达式如下
1 2 A[i - 1] 和B[j - 1]相等的时候,dp[i][j] = dp[i - 1][j - 1] + 1; 不等的时候dp[i][j] = max(dp[i-1][j], dp[i][j-1])
代码如下
1 2 3 4 5 6 7 8 9 10 11 12 13 14 class Solution (object ): def longestCommonSubsequence (self, text1, text2 ): n1 = len (text1) n2 = len (text2) dp = [[0 ]*(n2+1 ) for _ in range (n1+1 )] result = -1 for i in range (1 , n1+1 ): for j in range (1 , n2+1 ): if text1[i-1 ] == text2[j-1 ]: dp[i][j] = max (dp[i][j], dp[i-1 ][j-1 ] + 1 ) else : dp[i][j] = max (dp[i-1 ][j], dp[i][j-1 ]) result = max (result, dp[i][j]) return result
不相交的线[1035]
位于 https://leetcode.cn/problems/uncrossed-lines
和最长公共子序列一样,代码也是一样的
1 2 3 4 5 6 7 8 9 10 11 12 13 14 class Solution (object ): def maxUncrossedLines (self, nums1, nums2 ): n1 = len (nums1) n2 = len (nums2) dp = [[0 ]*(n2+1 ) for _ in range (n1+1 )] result = -1 for i in range (1 , n1+1 ): for j in range (1 , n2+1 ): if nums1[i-1 ] == nums2[j-1 ]: dp[i][j] = max (dp[i][j], dp[i-1 ][j-1 ] + 1 ) else : dp[i][j] = max (dp[i-1 ][j], dp[i][j-1 ]) result = max (result, dp[i][j]) return result
判断子序列[392]
位于 https://leetcode.cn/problems/is-subsequence
dp[i][j] 表示以下标i-1为结尾的字符串s,和以下标j-1为结尾的字符串t,相同子序列的长度为dp[i][j]。
代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 class Solution (object ): def isSubsequence (self, s, t ): n1 = len (s) n2 = len (t) dp = [[0 ]*(n2+1 ) for _ in range (n1+1 )] for i in range (1 , n1+1 ): for j in range (1 , n2+1 ): if s[i-1 ]==t[j-1 ]: dp[i][j] = max (dp[i][j],dp[i-1 ][j-1 ] + 1 ) else : dp[i][j] = max (dp[i-1 ][j], dp[i][j-1 ]) if dp[-1 ][-1 ]==len (s): return True return False
编辑距离[72]
位于 https://leetcode.cn/problems/edit-distance
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 : n1 = len (word1) n2 = len (word2) dp = [[0 ]*(n2+1 ) for _ in range (n1+1 )] for i in range (n1+1 ): dp[i][0 ] = i for j in range (n2+1 ): dp[0 ][j] = j for i in range (1 ,n1+1 ): for j in range (1 , n2+1 ): if word1[i-1 ]==word2[j-1 ]: dp[i][j] = dp[i-1 ][j-1 ] else : dp[i][j] = min (dp[i-1 ][j-1 ],dp[i-1 ][j],dp[i][j-1 ]) + 1 return dp[n1][n2]
不同的子序列[115]
位于 https://leetcode.cn/problems/distinct-subsequences
困难题动态规划方程不易想到
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 class Solution : def numDistinct (self, s: str , t: str ) -> int : n1 = len (s) n2 = len (t) dp = [[0 ]*(n2+1 ) for _ in range (n1+1 )] for i in range (n1+1 ): dp[i][0 ] = 1 for j in range (n2+1 ): dp[0 ][j] = 0 dp[0 ][0 ] = 1 for i in range (1 , n1+1 ): for j in range (1 , n2+1 ): if s[i-1 ]==t[j-1 ]: dp[i][j] = dp[i-1 ][j-1 ] + dp[i-1 ][j] else : dp[i][j] = dp[i-1 ][j] return dp[-1 ][-1 ]
两个字符串的删除操作[583]
位于 https://leetcode.cn/problems/delete-operation-for-two-strings
1 2 3 4 5 6 7 8 9 10 11 12 13 14 class Solution : def minDistance (self, word1: str , word2: str ) -> int : dp = [[0 ] * (len (word2)+1 ) for _ in range (len (word1)+1 )] for i in range (len (word1)+1 ): dp[i][0 ] = i for j in range (len (word2)+1 ): dp[0 ][j] = j for i in range (1 , len (word1)+1 ): for j in range (1 , len (word2)+1 ): if word1[i-1 ] == word2[j-1 ]: dp[i][j] = dp[i-1 ][j-1 ] else : dp[i][j] = min (dp[i-1 ][j-1 ] + 2 , dp[i-1 ][j] + 1 , dp[i][j-1 ] + 1 ) return dp[-1 ][-1 ]
爬楼梯类
斐波那契数[509]
位于 https://leetcode.cn/problems/fibonacci-number
1 2 3 4 5 6 7 8 9 10 11 12 class Solution : def fib (self, n: int ) -> int : if n==0 : return 0 if n==1 : return 1 dp = [0 ]*(n+1 ) dp[0 ] = 0 dp[1 ]= 1 for i in range (2 , n+1 ): dp[i] = dp[i-1 ] + dp[i-2 ] return dp[n]
爬楼梯[70]
位于 https://leetcode.cn/problems/climbing-stairs
1 2 3 4 5 6 7 8 9 10 11 12 class Solution : def climbStairs (self, n: int ) -> int : if n==1 : return 1 if n==2 : return 2 dp = [0 ] * (n+1 ) dp[0 ] = 1 dp[1 ] = 2 for i in range (2 ,n+1 ): dp[i] = dp[i-2 ] + dp[i-1 ] return dp[n-1 ]
爬楼梯2[三步问题]
1 2 3 4 5 6 7 8 9 10 11 12 class Solution : def waysToStep (self, n: int ) -> int : run_maps = {1 :1 ,2 :2 ,3 :4 } if n in run_maps: return run_maps[n] dp = [0 ] * (n+1 ) dp[1 ] = 1 dp[2 ] = 2 dp[3 ] = 4 for i in range (4 , n+1 ): dp[i] = (dp[i-1 ] + dp[i-2 ] + dp[i-3 ])%1000000007 return dp[n]
爬楼梯3使用最小花费爬楼梯[LCR 088]
位于 https://leetcode.cn/problems/min-cost-climbing-stairs
1 2 3 4 5 6 7 8 class Solution : def minCostClimbingStairs (self, cost: List [int ] ) -> int : dp = [0 ] * len (cost) dp[0 ] = cost[0 ] dp[1 ] = cost[1 ] for i in range (2 , len (cost)): dp[i] = min (dp[i-1 ], dp[i-2 ]) + cost[i] return min (dp[len (cost)-1 ],dp[len (cost)-2 ])
完全平方数[279]
位于 https://leetcode.cn/problems/perfect-squares
1 2 3 4 5 6 7 8 class Solution : def numSquares (self, n: int ) -> int : dp = [float ("inf" )] * (n+1 ) dp[0 ] = 0 for i in range (1 ,n+1 ): for j in range (int (i**0.5 )+1 ): dp[i] = min (dp[i], dp[i-j*j]+1 ) return dp[-1 ]
跳跃游戏[55]
位于 https://leetcode.cn/problems/jump-game
1 2 3 4 5 6 7 8 9 10 class Solution : def canJump (self, nums: List [int ] ) -> bool : dp = [0 ] * len (nums) dp[0 ] = 1 for i in range (1 , len (nums)): for j in range (i-1 ,-1 ,-1 ): if dp[j] and nums[j] + j >= i: dp[i] = 1 break return True if dp[len (nums)-1 ] else False
跳跃游戏II[45]
位于 https://leetcode.cn/problems/jump-game-ii
1 2 3 4 5 6 7 8 9 class Solution : def jump (self, nums: List [int ] ) -> int : dp = [float ("inf" )] * len (nums) dp[0 ] = 0 for i in range (1 , len (nums)): for j in range (i): if nums[j] + j >= i: dp[i] = min (dp[i], dp[j] + 1 ) return dp[-1 ]
跳跃游戏 III[1306]
位于 https://leetcode.cn/problems/jump-game-iii
代码如下:
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 31 32 33 class Solution : def canReach (self, arr: List [int ], start: int ) -> bool : from collections import deque used = {} queue = deque([start]) while queue: node = queue.popleft() if arr[node]==0 : return True if node in used: continue used[node] = 1 for v in [node-arr[node], node+arr[node]]: if v >= 0 and v<len (arr): if v in used: continue queue.append(v) return False class Solution : def canReach (self, arr: List [int ], start: int ) -> bool : s = {} def dfs (arr, st ): if st not in s and arr[st]==0 : return True s[st] = 1 current_step = arr[st] for step in [st+current_step, st-current_step]: if step>=0 and step< len (arr) and step not in s and dfs(arr, step): return True return False return dfs(arr, start)
跳跃游戏 VI[1696]
位于 https://leetcode.cn/problems/jump-game-vi
1 2 3 4 5 6 7 8 9 class Solution : def maxResult (self, nums: List [int ], k: int ) -> int : dp = [-float ("inf" )] * len (nums) dp[0 ] = nums[0 ] for i in range (1 , len (nums)): for j in range (max (0 , i - k), i): dp[i] = max (dp[i], dp[j] + nums[i]) return dp[-1 ]
青蛙过河[403]
位于 https://leetcode.cn/problems/frog-jump
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 31 32 33 class Solution : def canCross (self, stones: List [int ] ) -> bool : for i in range (1 , len (stones)): if stones[i] > i + stones[i-1 ]: return False end = stones[-1 ] stones = set (stones) @functools.lru_cache(None ) def dfs (location, step ): if location == end: return True for step in [step-1 , step, step+1 ]: if step > 0 and location+step in stones and dfs(location+step, step): return True return False return dfs(0 , 0 ) class Solution : def canCross (self, stones: List [int ] ) -> bool : n = len (stones) dp = [[False ]* n for _ in range (n)] dp[0 ][0 ] = True for i in range (1 ,n): for j in range (i-1 ,-1 ,-1 ): k = stones[i] - stones[j] if k>j+1 : break dp[i][k] = dp[j][k] or dp[j][k-1 ] or dp[j][k+1 ] for i in dp[n-1 ]: if i: return True return False
不懂看这里 https://www.ai2news.com/blog/2980406/
打家劫舍[198]
位于 https://leetcode.cn/problems/house-robber
1 2 3 4 5 6 7 8 9 10 11 12 13 14 class Solution : def rob (self, nums: List [int ] ) -> int : if len (nums)==1 : return nums[0 ] if len (nums)==2 : return max (nums) dp = [0 ] * len (nums) dp[0 ] = nums[0 ] dp[1 ] = nums[1 ] for i in range (2 ,len (nums)): for j in range (i-1 ): dp[i] = max (dp[i], dp[j]+nums[i]) return max (dp)
打家劫舍 II[213]
位于 https://leetcode.cn/problems/house-robber-ii
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 class Solution : def rob (self, nums: List [int ] ) -> int : def dp_f (nums ): dp = [0 ] * len (nums) dp[0 ] = nums[0 ] dp[1 ] = nums[1 ] for i in range (2 ,len (nums)): for j in range (i-1 ): dp[i] = max (dp[i], dp[j]+nums[i]) return max (dp) if len (nums)==1 : return nums[0 ] if len (nums)==2 : return max (nums) max_dp1 = dp_f(nums[0 :len (nums)-1 ]) max_dp2 = dp_f(nums[1 :len (nums)]) return max (max_dp1, max_dp2)
分开来做就好
打家劫舍 III[337]
位于 https://leetcode.cn/problems/house-robber-iii
递归法,实现思路:用root点和不用root点
1 2 3 4 5 6 7 8 9 10 11 class Solution : def rob (self, root: Optional [TreeNode] ) -> int : if not root: return 0 money = root.val if root.left: money = money + self.rob(root.left.left) + self.rob(root.left.right) if root.right: money = money + self.rob(root.right.left) + self.rob(root.right.right) return max (money, self.rob(root.left)+self.rob(root.right))
递归优化法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 class Solution : def __init__ (self ): self.d = {} def rob (self, root: Optional [TreeNode] ) -> int : if not root: return 0 if root in self.d: return self.d[root] money = root.val if root.left: money = money + self.rob(root.left.left) + self.rob(root.left.right) if root.right: money = money + self.rob(root.right.left) + self.rob(root.right.right) result = max (money, self.rob(root.left)+self.rob(root.right)) self.d[root] = result return result
背包零钱
背包问题介绍
可以看如下几个连接,增加对某些点的理解
整体上,背包问题的动态规划写成如下的形式:
0-1背包
1 dp[i][j]=max(dp[i-1][j],dp[i-1][j-w_i]+v_i, 0<=w_i<=j)
完全背包
1 dp[i][j]=max(dp[i-1][j],dp[i][j-w_i]+v_i, 0<=w_i<=j)
注意:在使用的时候,大部分组合问题,因此for两层循环的话,外层是N个物件或者N种币,内层是背包的容量W或者是要凑的零钱大小W。关于组合还有排序的问题可以看 https://leetcode.cn/problems/coin-change-ii/solutions/143948/ling-qian-dui-huan-iihe-pa-lou-ti-wen-ti-dao-di-yo/
逻辑分开
做题的逻辑哈
模板
1 2 3 4 5 6 7 8 9 10 11 12 dp[0 ][0 ] = x for i in range (1 , n+1 ): for j in range (amount+1 ): if xx: dp[i][j] = min (dp[i-1 ][j] , dp[i][j-coins[i-1 ]]+1 ) else : ... for i in coins: for j in range (amount,i-1 , -1 ): dp[j] = min ,max ,..., dp[j], dp[j-i]
0-1背包
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 N = 3 W = 4 Wt = [2 , 1 , 3 ] val = [4 , 2 , 3 ] dp = [[0 ] * (W + 1 ) for _ in range (N + 1 )] for i in range (1 , N + 1 ): for w in range (1 , W + 1 ): if w - Wt[i - 1 ] < 0 : dp[i][w] = dp[i - 1 ][w] else : dp[i][w] = max (dp[i - 1 ][w], dp[i - 1 ][w - Wt[i - 1 ]] + val[i - 1 ]) def test_1_wei_bag_problem (): weight = [1 , 3 , 4 ] value = [15 , 20 , 30 ] bagWeight = 4 dp = [0 ] * (bagWeight + 1 ) for i in range (len (weight)): for j in range (bagWeight, weight[i] - 1 , -1 ): dp[j] = max (dp[j], dp[j - weight[i]] + value[i]) print (dp[bagWeight])
目标和[494]
位于 https://leetcode.cn/problems/target-sum
分析:0-1背包,外循环硬币,内循环金额,内循环逆序,注意内循环最小值
dp累加dp[i] = dp[i] + dp[j-i]
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 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 class Solution (object ): def findTargetSumWays (self, nums, target ): """ :type nums: List[int] :type target: int :rtype: int """ total = sum (nums) if abs (target) > total: return 0 if (total+target)%2 ==1 : return 0 pos = (total + target) // 2 neg = (total - target) // 2 C = min (pos, neg) n = len (nums) dp = [[0 ]*(C+1 ) for _ in range (n+1 )] dp[0 ][0 ] = 1 for i in range (1 , n+1 ): for j in range (C+1 ): if j >= nums[i-1 ]: dp[i][j] = dp[i-1 ][j] + dp[i-1 ][j-nums[i-1 ]] else : dp[i][j] = dp[i-1 ][j] return dp[n][C] class Solution (object ): def findTargetSumWays (self, nums, target ): """ :type nums: List[int] :type target: int :rtype: int """ total = sum (nums) if abs (target) > total: return 0 if (total+target)%2 ==1 : return 0 pos = (total + target) // 2 neg = (total - target) // 2 C = min (pos, neg) n = len (nums) dp = [0 ] * (C+1 ) dp[0 ] = 1 for i in nums: for j in range (C, i-1 , -1 ): dp[j] = dp[j] + dp[j-i] return dp[C]
分割等和子集[416]
位于 https://leetcode.cn/problems/partition-equal-subset-sum
分析:0-1背包,外循环硬币,内循环金额,内循环逆序,注意内循环最小值
dp累求|即可,为dp[i] = dp[i] | dp[j-i]
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 class Solution (object ): def canPartition (self, nums ): total = sum (nums) if total % 2 ==1 : return False target = total // 2 if max (nums) > target: return False dp = [False ] * (target+1 ) dp[0 ] = True for num in nums: for j in range (target, num-1 , -1 ): dp[j] |= dp[j-num] return dp[target]
最后一块石头的重量 II[1049]
位于 https://leetcode.cn/problems/last-stone-weight-ii
分析:0-1背包,外循环硬币,内循环金额,内循环逆序,注意内循环最小值
dp为 dp[j] = max(dp[j], dp[j - stone] + stone)
分析:本题其实就是尽量让石头分成重量相同的两堆,相撞之后剩下的石头最小,这样就化解成01背包问题了。分析到物品的重量为stones[i],物品的价值也为stones[i]。
dp数组含义,表示前i个stone得到不超过taget的最大重量,这里是最大,不是刚刚好。
1 2 3 4 5 6 7 8 9 10 11 12 class Solution : def lastStoneWeightII (self, stones: List [int ] ) -> int : dp = [0 ] * 15001 total_sum = sum (stones) target = total_sum // 2 for stone in stones: for j in range (target, stone - 1 , -1 ): dp[j] = max (dp[j], dp[j - stone] + stone) return total_sum - dp[target] - dp[target]
一和零[474]
位于 https://leetcode.cn/problems/ones-and-zeroes
分析:0-1背包,外循环硬币,内循环金额,内循环逆序,注意内循环最小值
dp表达式为 dp[m][n] = max(dp[m][n], dp[m-neg][n-pos]+1)
分析:从一个list的字符串中选择一些数,使得1的数不超过m,0的数不超过n.
dp[i][j]表示不超过i和j的最大集合的长度,和上面类似,这是这里二维。
1 2 3 4 5 6 7 8 9 10 class Solution (object ): def findMaxForm (self, strs, m, n ): dp = [[0 ]*(n+1 ) for _ in range (m+1 )] for s in strs: pos = s.count('1' ) neg = len (s) - pos for m1 in range (m,neg-1 ,-1 ): for n1 in range (n,pos-1 ,-1 ): dp[m1][n1] = max (dp[m1][n1], dp[m1-neg][n1-pos]+1 ) return dp[m][n]
完全背包
1 2 3 4 5 6 7 8 9 10 11 N = 3 W = 4 Wt = [2 , 1 , 3 ] val = [4 , 2 , 3 ] dp = [[0 ] * (W + 1 ) for _ in range (N + 1 )] for i in range (1 , N + 1 ): for w in range (1 , W + 1 ): if w - Wt[i - 1 ] < 0 : dp[i][w] = dp[i - 1 ][w] else : dp[i][w] = max (dp[i - 1 ][w], dp[i][w - Wt[i - 1 ]] + val[i - 1 ])
零钱兑换[322]
位于 https://leetcode.cn/problems/coin-change
分析:完全背包,外循环硬币,内循环金额,内循环顺序
dp公式dp[j] = min(dp[j],dp[j-i]+1)
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 31 32 33 34 35 36 37 38 39 40 41 class Solution (object ): def coinChange (self, coins, amount ): N = len (coins) dp = [[float ("inf" )]*(amount+1 ) for _ in range (N+1 )] dp[0 ][0 ] = 0 for i in range (1 ,N+1 ): for j in range (amount+1 ): if j >= coins[i-1 ]: dp[i][j] = min (dp[i-1 ][j] , dp[i][j-coins[i-1 ]]+1 ) else : dp[i][j] = dp[i-1 ][j] ans = dp[N][amount] return ans if ans!=float ("inf" ) else -1 class Solution (object ): def coinChange (self, coins, amount ): N = len (coins) dp = [float ("inf" )]*(amount+1 ) dp[0 ] = 0 for i in range (N): for j in range (amount+1 ): if j >= coins[i]: dp[j] = min (dp[j], dp[j-coins[i]]+1 ) return dp[amount] if dp[amount]!=float ("inf" ) else -1 class Solution (object ): def coinChange (self, coins, amount ): if amount==0 : return 0 dp = [float ('inf' )] * (amount+1 ) dp[0 ] = 0 for i in coins: for j in range (i, amount+1 ): dp[j] = min (dp[j],dp[j-i]+1 ) return dp[amount] if dp[amount]!=float ('inf' ) else -1
注意一维和二维中,在二维初始化的时候长度是amount+1, 一维也是,但是在N这个地方,二维是N+1,一维是N。
解法 https://leetcode.cn/problems/coin-change/solutions/1412324/by-flix-su7s/
零钱兑换II[518]
位于 https://leetcode.cn/problems/coin-change-ii
分析:完全背包,外循环硬币,内循环金额,内循环顺序
dp公式dp[j] = dp[j] + dp[j-i]
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 class Solution (object ): def change (self, amount, coins ): N = len (coins) dp = [[0 ]*(amount+1 ) for _ in range (N+1 )] dp[0 ][0 ] = 1 for i in range (1 , N+1 ): for j in range (amount+1 ): if coins[i-1 ] > j: dp[i][j] = dp[i-1 ][j] else : dp[i][j] = dp[i-1 ][j] + dp[i][j-coins[i-1 ]] return dp[N][amount]
当然,官方简单的方法如下:
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 class Solution (object ): def change (self, amount, coins ): if not amount: return 1 dp = [0 ] * (amount+1 ) dp[0 ] = 1 N = len (coins) for i in range (N): for j in range (amount+1 ): if j >= coins[i]: dp[j] = dp[j] + dp[j-coins[i]] return dp[amount] class Solution : def change (self, amount: int , coins: List [int ] ) -> int : if not amount: return 1 dp = [0 ] * (amount+1 ) dp[0 ] = 1 for i in coins: for j in range (i, amount+1 ): dp[j] = dp[j] + dp[j-i] return dp[amount]
硬币问题[面试题 08.11]
题目见 https://leetcode-cn.com/problems/coin-lcci 题解如下:
1 2 3 4 5 6 7 8 9 10 class Solution : def waysToChange (self, n: int ) -> int : mod = 10 **9 + 7 coins = [25 , 10 , 5 , 1 ] f = [1 ] + [0 ] * n for coin in coins: for i in range (coin, n + 1 ): f[i] += f[i - coin] return f[n] % mod
分割数组的最大值[410]
位于 https://leetcode.cn/problems/split-array-largest-sum
分析:完全背包,外循环硬币,内循环金额,内循环顺序
dp公式f[i][j] = min(f[i][j], max(f[k][j - 1], sub[i] - sub[k]))
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 class Solution : def splitArray (self, nums: List [int ], m: int ) -> int : n = len (nums) f = [[10 **18 ] * (m + 1 ) for _ in range (n + 1 )] sub = [0 ] for elem in nums: sub.append(sub[-1 ] + elem) f[0 ][0 ] = 0 for i in range (1 , n + 1 ): for j in range (1 , min (i, m) + 1 ): for k in range (i): f[i][j] = min (f[i][j], max (f[k][j - 1 ], sub[i] - sub[k])) return f[n][m]
组数总和Ⅳ
位于 https://leetcode.cn/problems/combination-sum-iv
分析:完全背包,外循环金额【排列问题】,内循环硬币,内循环顺序
dp公式dp[i] = dp[i] + dp[i-j]
题目见 https://leetcode-cn.com/problems/combination-sum-iv/ 这道题和爬楼梯的问题有点类似,但是解法不太一样。动态规划的方程是
dp[i] = sum(dp[i-j]) j<=i.解法如下
1 2 3 4 5 6 7 8 9 class Solution : def combinationSum4 (self, nums: List [int ], target: int ) -> int : dp = [0 ] * (target+1 ) dp[0 ] = 1 for i in range (1 ,target+1 ): for j in nums: if i >= j: dp[i] = dp[i] + dp[i-j] return dp[-1 ]
爬楼梯[70]
位于 https://leetcode.cn/problems/climbing-stairs
分析:完全背包,外循环金额【排列问题】,内循环硬币,内循环顺序
dp公式dp[i] = dp[i] + dp[i-j]
1 2 3 4 5 6 7 8 target = 10 w = [1 , 2 , 3 ] dp = [0 ] * (target + 1 ) for i in range (target + 1 ): for j in range (len (w)): if i - j >= 0 : dp[i] = dp[i] + dp[i - j] print (dp[10 ])
剪绳子
题目见
股票买卖问题
模板
这里主要说一下DP的思路,按照labuladong的做法,基本上上可以在一个模板上丝毫不动,就可以得到结果
统一的动态规划方程如下
1 2 3 4 5 6 7 8 9 10 11 # 初始化 dp = [[0 ]*2 for _ in range (len (prics))] for i in range (len (prices)): dp[i][0 ] = 0 dp[i][1 ] = -float("inf" ) if i==0 : dp[i][0 ] = 0 dp[i][1 ] = -prics[0 ] - 手续费 # for 一下 dp[i][k][0 ] = max(dp[i-1 ][k][0 ], dp[i-1 ][k][1 ] + prices[i]) dp[i][k][1 ] = max(dp[i-1 ][k][1 ], dp[i-1 ][k-1 ][0 ] - prices[i] - 手续费)
i表示第几天,k表示最多交易次数,0表示手里没股票了,1表示手里还有股票。
https://labuladong.github.io/algo/di-er-zhan-a01c6/yong-dong--63ceb/yi-ge-fang-3b01b/
买卖股票的最佳时机[121]
题目:https://leetcode.cn/problems/best-time-to-buy-and-sell-stock/description/
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 class Solution (object ): def maxProfit (self, prices ): dp = [[0 ]*2 for i in range (len (prices))] for i in range (len (prices)): if i == 0 : dp[i][0 ] = 0 dp[i][1 ] = -prices[0 ] continue dp[i][0 ] = max (dp[i-1 ][0 ], dp[i-1 ][1 ] + prices[i]) dp[i][1 ] = max (dp[i-1 ][1 ], -prices[i]) return dp[len (prices)-1 ][0 ] class Solution (object ): def maxProfit (self, prices ): mins = float ("inf" ) profit = 0 for i in prices: if i < mins: mins = i profit = max (profit, i - mins) return profit
买卖股票的最佳时机II[122]
题目:https://leetcode.cn/problems/best-time-to-buy-and-sell-stock-ii/description/
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 class Solution (object ): def maxProfit (self, prices ): dp = [[0 ]*2 for i in range (len (prices))] for i in range (len (prices)): if i == 0 : dp[i][0 ] = 0 dp[i][1 ] = -prices[0 ] continue dp[i][0 ] = max (dp[i-1 ][0 ], dp[i-1 ][1 ] + prices[i]) dp[i][1 ] = max (dp[i-1 ][1 ], dp[i-1 ][0 ]-prices[i]) return dp[len (prices)-1 ][0 ] class Solution (object ): def maxProfit (self, prices ): profit = 0 for i in range (1 , len (prices)): if prices[i] - prices[i-1 ] > 0 : profit += prices[i] - prices[i-1 ] return profit
买卖股票的最佳时机含冷冻期[309]
题目:https://leetcode.cn/problems/best-time-to-buy-and-sell-stock-with-cooldown/
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 class Solution (object ): def maxProfit (self, prices ): dp = [[0 ]*2 for i in range (len (prices))] for i in range (len (prices)): if i == 0 : dp[i][0 ] = 0 dp[i][1 ] = -prices[0 ] continue dp[i][0 ] = max (dp[i-1 ][0 ], dp[i-1 ][1 ] + prices[i]) dp[i][1 ] = max (dp[i-1 ][1 ], dp[i-2 ][0 ]-prices[i]) return dp[len (prices)-1 ][0 ]
买卖股票的最佳时机含手续费[714]
题目:https://leetcode.cn/problems/best-time-to-buy-and-sell-stock-with-transaction-fee/description/
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 class Solution (object ): def maxProfit (self, prices, fee ): dp = [[0 ]*2 for i in range (len (prices))] for i in range (len (prices)): if i == 0 : dp[i][0 ] = 0 dp[i][1 ] = -prices[0 ] - fee continue dp[i][0 ] = max (dp[i-1 ][0 ], dp[i-1 ][1 ] + prices[i]) dp[i][1 ] = max (dp[i-1 ][1 ], dp[i-1 ][0 ]-prices[i]-fee) return dp[len (prices)-1 ][0 ]
买卖股票的最佳时机 III[123]
题目:https://leetcode.cn/problems/best-time-to-buy-and-sell-stock-iii/description/
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 class Solution (object ): def maxProfit (self, prices ): """ :type prices: List[int] :rtype: int """ max_k = 2 dp = [[[0 ] * 2 for _ in range (max_k + 1 )] for _ in range (len (prices))] for i in range (len (prices)): for k in range (1 , max_k+1 ): if i == 0 : dp[i][k][0 ] = 0 dp[i][k][1 ] = -prices[0 ] continue dp[i][k][0 ] = max (dp[i-1 ][k][0 ], dp[i-1 ][k][1 ] + prices[i]) dp[i][k][1 ] = max (dp[i-1 ][k][1 ], dp[i-1 ][k-1 ][0 ]-prices[i]) return dp[len (prices)-1 ][max_k][0 ]
博弈问题
https://labuladong.online/algo/dynamic-programming/game-theory/
预测赢家[486]
题目:https://leetcode.cn/problems/predict-the-winner/description/
1 2 3 4 5 6 7 8 9 10 11 class Solution : def predictTheWinner (self, nums: List [int ] ) -> bool : length = len (nums) dp = [[0 ] * length for _ in range (length)] for i, num in enumerate (nums): dp[i][i] = num for l in range (1 ,length): for i in range (length-l): j = i + l dp[i][j] = max (nums[i] - dp[i + 1 ][j], nums[j] - dp[i][j - 1 ]) return dp[0 ][length - 1 ] >= 0
石子游戏[877]
题目:https://leetcode.cn/problems/stone-game/description/
1 2 3 4 5 6 7 8 9 10 11 class Solution : def stoneGame (self, piles: List [int ] ) -> bool : length = len (piles) dp = [[0 ] * len (piles) for _ in range (len (piles))] for i in range (len (piles)): dp[i][i] = piles[i] for l in range (1 ,length): for i in range (length-l): j = i + l dp[i][j] = max (piles[i] - dp[i + 1 ][j], piles[j] - dp[i][j - 1 ]) return dp[0 ][length - 1 ] > 0
和上面一样,严格意义上,不算双序列问题,所以这里不用dp=[[0]*(n+1) for i in range(n+1)]