1.主要思想
简单来说就是将大问题划分为小问题,并解决小问题重复计算的问题。
2.适用场景
常常适用于有重叠子问题和最优子结构性质的问题。
3.模板步骤
- 确定动态规划状态
- 写出状态转移方程(画出状态转移表)
- 考虑初始化的条件
- 考虑输出状态
- 考虑对时间和空间复杂度的优化(Bonus)
4.Leetcode题解
-
题目
给定一个未经排序的整数数组,找到最长且连续的的递增序列,并返回该序列的长度。
-
思路
- 确定状态:re_ls[i]为nums[i]为结尾的最长且连续的递增子序列的长度
- 转移方程:
若nums[i-1] < nums[i],则re_ls[i]的长度在上一状态基础上加1即可;否则无法满足连续这一条件,re_ls[i]长度维持初始1即可
方程是re_ls[i] = re_ls[i-1] + 1
- 初始条件:re_ls中全为1
- 输出状态:re_ls中的最大值即为所求
-
代码
class Solution: def findLengthOfLCIS(self, nums: List[int]) -> int: if len(nums) == 0: return 0 re_ls = [1]*len(nums) for i in range(1,len(nums)): if nums[i-1] < nums[i]: re_ls[i] += re_ls[i-1] return max(re_ls)
-
题目
给定一个字符串 s,找到 s 中最长的回文子串。你可以假设 s 的最大长度为 1000。
-
思路
- 确定状态:使用二维的dp数组来记录状态,dp[i][j]表示字符串s从i到j是回文子串
- 转移方程:
对于字符串dp[i][j],如果s[i]等于s[j],则状态等同于子字符串dp[i+1][j-1];又因字符串长度小于2时,肯定是回文子串,故 (j-1)-(i+1) + 1<2时,dp[i][j]=True
方程是if s[i]==s[j]: if j-i<3: dp[i][j]=3 else: dp[i][j]=dp[i+1][j-1]
- 初始条件:初始状态为False ,对角线dp[i][i]=True
- 输出状态:用一个变量存储目前已知回文子串的长度中最长的
- 考虑优化:
-
代码
class Solution: def longestPalindrome(self, s: str) -> str: if len(s) < 2: return s dp = [[False]*len(s) for _ in range(len(s))] max_len=1 start = 0 for i in range(len(s)): dp[i][i] = True for j in range(1,len(s)): for i in range(j): if s[i]==s[j]: if j-i<3: dp[i][j]=True else: dp[i][j] = dp[i+1][j-1] if dp[i][j] and j-i+1 > max_len: max_len = j-i+1 start=i return s[start:start+max_len]
-
题目
给定一个字符串 s ,找到其中最长的回文子序列,并返回该序列的长度。可以假设 s 的最大长度为 1000 。
-
思路
- 确定状态:dp[i][j]表示字符串第i个字符到第j个字符最长的回文子序列长度
- 转移方程:
if s[i]==s[j]: dp[i][j] = dp[i+1][j-1] + 2 else: dp[i][j] = max(dp[i][j-1],dp[i+1][j])
- 初始条件:对角线为1,其他位置为0
- 输出状态:右上角即为所求
- 考虑优化
-
代码
class Solution: def longestPalindromeSubseq(self, s: str) -> int: n=len(s) dp = [[0]*n for _ in range(n)] for i in range(n): dp[i][i] = 1 for i in range(n,-1,-1): for j in range(i+1,n): if s[i]==s[j]: dp[i][j]= dp[i+1][j-1]+2 else: dp[i][j]=max(dp[i][j-1],dp[i+1][j]) return dp[0][-1]
-
题目
给定两个单词 word1 和 word2,计算出将 word1 转换成 word2 所使用的最少操作数 。
-
思路
- 确定状态:dp[i][j]为字符串word1长度为i和字符串word2长度为j时,word1转化成word2所执行的最少操作次数的值。
- 转移方程:
if word1[i]==word2[j]: dp[i][j] = dp[i-1][j-1] else: dp[i][j] = min(dp[i][j-1]+1,dp[i-1][j]+1,dp[i-1][j-1]+1)
- 初始条件:dp[i][0] = i,dp[0][j] = j,其他位置为0
- 输出状态:dp[-1][-1]
- 考虑优化
-
代码
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],dp[i][j-1],dp[i-1][j-1])+1 return dp[-1][-1]
-
题目
你是一个专业的小偷,计划偷窃沿街的房屋。每间房内都藏有一定的现金,影响你偷窃的唯一制约因素就是相邻的房屋装有相互连通的防盗系统,如果两间相邻的房屋在同一晚上被小偷闯入,系统会自动报警。
-
思路
- 确定状态:dp[i]表示偷窃第i个房子时能得到的最高金额
- 转移方程:dp[i] = max(dp[i-2]+nums[i],dp[i-1])
- 初始条件:dp[0] = nums[0],dp[1] = max(nums[0],nums[1])
- 输出状态:dp[-1]
- 考虑优化
-
代码
class Solution: def rob(self, nums: List[int]) -> int: if len(nums) == 0: return 0 elif len(nums) == 1: return nums[0] dp = [0]*len(nums) dp[0] = nums[0] dp[1] = max(nums[0],nums[1]) for i in range(2,len(nums)): dp[i] = max(dp[i-2]+nums[i],dp[i-1]) return dp[-1]
-
题目
你是一个专业的小偷,计划偷窃沿街的房屋,每间房内都藏有一定的现金。这个地方所有的房屋都围成一圈,这意味着第一个房屋和最后一个房屋是紧挨着的。同时,相邻的房屋装有相互连通的防盗系统,如果两间相邻的房屋在同一晚上被小偷闯入,系统会自动报警。
-
思路
- 确定状态:dp[i]表示偷窃第i个房子时能得到的最高金额
- 转移方程:
偷窃第一个房子,对应nums[:len(nums)-1],最大金额是v1;偷窃了最后一个房子,对应的是nums[1:],最大金额是v2;故转移方程和上题一致
dp[i] = max(dp[i-2]+nums[i],dp[i-1]) - 初始条件:dp[0] = nums[0],dp[1] = max(nums[0],nums[1])
- 输出状态:max(v1,v2)
- 考虑优化
-
代码
class Solution: def findValue(self, nums): dp = [0]*len(nums) dp[0] = nums[0] dp[1] = max(nums[0],nums[1]) for i in range(2,len(nums)): dp[i] = max(dp[i-2]+nums[i],dp[i-1]) return dp[-1] def rob(self, nums: List[int]) -> int: if len(nums) == 0: return 0 elif len(nums) == 1: return nums[0] elif len(nums) == 2: return max(nums) return max(self.findValue(nums[1:]),self.findValue(nums[:-1]))
参考:https://github.com/datawhalechina/team-learning-program/blob/master/LeetCodeClassification/2.动态规划.md