动态规划
动态规划常常适用于有重叠子问题和最优子结构性质的问题,动态规划方法所耗时间往往远少于朴素解法。
打家劫舍系列
198. 打家劫舍
198. 打家劫舍(简单)
class Solution:
def rob(self, nums: List[int]) -> int:
n = len(nums)
if n==0:
return 0
if n==1:
return nums[0]
dp = [0 for _ in range(len(nums))]
dp[0] = nums[0]
dp[1] = max(nums[0],nums[1])
for i in range(2,n):
dp[i] = max(dp[i-1],dp[i-2]+nums[i])
return dp[-1]
只与前两个数有关,因此可以进行如下优化
class Solution:
def rob(self, nums: List[int]) -> int:
n = len(nums)
if n==0:
return 0
if n==1:
return nums[0]
prev2 = nums[0] #i-2,dp[0]
prev1 = max(nums[0],nums[1]) #i-1,dp[1]
for i in range(2,n):
temp = max(prev1,prev2+nums[i])
prev2 = prev1
prev1 = temp
return prev1
213. 打家劫舍II
213. 打家劫舍II(中等)
所有房屋围成一圈,即第一间和最后一间房屋不能同时偷窃,因此分为两种情况
- 第一间到倒数第二间
- 第二间到最后一间
- 取max
class Solution:
def rob(self, nums: List[int]) -> int:
if not nums:
return 0
n = len(nums)
if n==1:
return nums[0]
if n==2:
return max(nums[0],nums[1])
return max(self.dp(nums[:-1]),self.dp(nums[1:]))
def dp(self,nums):
prev2 = 0
prev1 = 0
for i in range(0,len(nums)):
temp = max(prev2+nums[i],prev1)
prev2 = prev1
prev1 = temp
return prev1
337. 打家劫舍 III
337. 打家劫舍 III(中等)
思路:
为每个节点设置两个数,分别表示偷/不偷时的最高金额;
- 偷,则左右子节点都只能不偷
- 不偷,则取左右子节点的max再求和
# Definition for a binary tree node.
# class TreeNode:
# def __init__(self, x):
# self.val = x
# self.left = None
# self.right = None
class Solution:
def rob(self, root: TreeNode) -> int:
if not root:
return 0
res = self.helper(root)
return max(res)
def helper(self,root):
if not root:
return (0,0)
if root.left==None and root.right==None:
return (root.val,0)
left = self.helper(root.left)
right = self.helper(root.right)
res = [0,0]
res[0] = left[1]+right[1]+root.val
res[1] = max(left[0],left[1])+max(right[0],right[1])
return res
LeetCode一维dp问题
300. 最长上升子序列,LIS
300. 最长上升子序列(中等)
首先,需要对「子序列」和「子串」这两个概念进行区分;
- 子序列(subsequence)
子序列并不要求连续,例如:序列 [4, 6, 5] 是 [1, 2, 4, 3, 7, 6, 5] 的一个子序列。- 子串(substring、subarray)
子串一定是连续的,例如:「力扣」第 3 题:“无重复字符的最长子串”,「力扣」第 53 题:“最大子序和”。
方法一: 动态规划
定义dp数组,dp[i]表示以nums[i]为结尾的最长上升子序列的长度
class Solution:
def lengthOfLIS(self, nums: List[int]) -> int:
n = len(nums)
if n<=1:
return n
dp = [1 for _ in range(n)]
res = 1
for i in range(1,n):
for j in range(i-1,-1,-1):
if nums[i]>nums[j]:
dp[i] = max(dp[i],dp[j]+1)
res = max(res,dp[i])
return res
复杂度分析:
时间复杂度:O(n2)
空间复杂度:O(n),额外的dp数组
方法二: 贪心+二分
思路:
- 依旧着眼于上升子序列结尾的元素
- 已经得到的上升子序列结尾元素越小,构成更长的上升子序列的可能性更大
- 在上升子序列长度固定的情况下,记录结尾最小的元素的数组tail
tail数组的长度就是最长上升子序列的长度
过程:
遍历nums;
- 如果nums[i]>tail[-1],nums[i]加入tail数组;
- 否则,找到tail中第一个大于等于nums[i]的数(二分),使它变小。
class Solution:
def lengthOfLIS(self, nums: List[int]) -> int:
n = len(nums)
if n<=1:
return n
tail = [nums[0]]
for i in range(1,n):
if nums[i]>tail[-1]:
tail.append(nums[i])
continue
l = 0
r = len(tail)-1
flag = True
while l<=r:
mid = (l+r)>>1
if tail[mid]==nums[i]:
flag = False
break
elif tail[mid]>nums[i]:
r = mid-1
else:
l = mid+1
if flag:
tail[l] = nums[i]
return len(tail)
674. 最长连续递增序列
674. 最长连续递增序列(简单)
class Solution:
def findLengthOfLCIS(self, nums: List[int]) -> int:
n = len(nums)
if n<2:
return n
dp = [1 for _ in range(n)]
res = 1
for i in range(1,n):
if nums[i]>nums[i-1]:
dp[i] = dp[i-1]+1
else:
dp[i] = 1
res = max(res,dp[i])
return res
压缩空间,优化如下:
class Solution:
def findLengthOfLCIS(self, nums: List[int]) -> int:
n = len(nums)
if n<2:
return n
prev = 1
res = 1
for i in range(1,n):
if nums[i]>nums[i-1]:
temp = prev+1
else:
temp = 1
prev = temp
res = max(res,temp)
return res
LeetCode二维dp问题
1143. 最长公共子序列,LCS
1143. 最长公共子序列
思路:
定义二维数组,dp[i][j]表示text1[0:i]和text2[0:j]的最长公共子序列长度。
class Solution:
def longestCommonSubsequence(self, text1: str, text2: str) -> int:
if not text1 or not text2:
return 0
n1 = len(text1)
n2 = len(text2)
dp = [[0 for _ in range(n2+1)] for _ in range(n1+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] = dp[i-1][j-1]+1
else:
dp[i][j] = max(dp[i-1][j],dp[i][j-1])
return dp[-1][-1]
只与前一行和本行中前面的元素有关,dp压缩为2*(n2+1)
class Solution:
def longestCommonSubsequence(self, text1: str, text2: str) -> int:
if not text1 or not text2:
return 0
n1 = len(text1)
n2 = len(text2)
dp = [[0 for _ in range(n2+1)] for _ in range(2)]
i = 1
for i in range(1,n1+1):
for j in range(1,n2+1):
if text1[i-1]==text2[j-1]:
dp[i%2][j] = dp[(i-1)%2][j-1]+1
else:
dp[i%2][j] = max(dp[(i-1)%2][j],dp[i%2][j-1])
return dp[i%2][-1]
5. 最长回文子串
5. 最长回文子串(中等)
一、确定动态规划状态
用两个指针来记录子串的位置——二维dp数组记录
题目要求返回最长的回文子串——dp数组记录True/False
dp[i][j]表示子串s从i到j是否为回文子串
二、状态转移方程
- 子串首尾字符不相等时,肯定不是回文
- 子串首尾字符相等时
- 如果s[i+1][j-1]是回文,则s[i][j]是回文;
- 否则不是;
- 如果s[i+1][j-1]是空串或长度为1,则子串是回文(j-1-(i+1)+1<2,即j-i<3)
if s[i]==s[j]:
if j-i<3:
dp[i][j]=True
else:
dp[i][j]=dp[i+1][j-1]
注意填表顺序!!!
class Solution:
def longestPalindrome(self, s: str) -> str:
if len(s)<=1:
return s
n = len(s)
dp = [[False for _ in range(n)]for _ in range(n)]
max_len = 1#记录最长的长度
start = 0 #记录最长回文子串的初始位置
for j in range(1,n):
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]:
cur_len = j-i+1
if cur_len>max_len:
max_len = cur_len
start = i
return s[start:start+max_len]
516. 最长回文子序列
516. 最长回文子序列(中等)
class Solution:
def longestPalindromeSubseq(self, s: str) -> int:
n = len(s)
if n<2:
return n
dp = [[0 for _ in range(n)] for _ in range(n)]#dp[i][j]
for i in range(n):
dp[i][i] = 1
for j in range(1,n):
for i in range(j-1,-1,-1):
if s[i]!=s[j]:
dp[i][j] = max(dp[i+1][j],dp[i][j-1])
else:
dp[i][j] = dp[i+1][j-1]+2
return dp[0][-1]
72. 编辑距离
72. 编辑距离(困难)
一、确定动态规划状态
设计两个字符串,考虑二维dp数组来保存转移状态;
定义dp[i][j]表示word1[0:i]转化为word2[0:j]所执行的最少操作次数。
二、状态转移方程
word1[i] == word2[j]时,dp[i][j]=dp[i-1][j-1]
word1[i] != word2[j]时,dp[i][j]=1+min(dp[i-1][j],dp[i][j-1],dp[i-1][j-1])
- 插入:dp[i][j-1]+1
- 删除:dp[i-1][j]+1
- 替换:dp[i-1][j-1]+1
class Solution:
def minDistance(self, word1: str, word2: str) -> int:
if not word1 and not word2:
return 0
l1 = len(word1)
l2 = len(word2)
dp = [[0 for _ in range(l2+1)] for _ in range(l1+1)]
for i in range(l1+1):
dp[i][0] = i
for j in range(1,l2+1):
dp[0][j] = j
for i in range(1,l1+1):
for j in range(1,l2+1):
if word1[i-1]==word2[j-1]:
dp[i][j] = dp[i-1][j-1]
else:
dp[i][j]=1+min(dp[i-1][j],dp[i][j-1],dp[i-1][j-1])
#print(dp)
return dp[-1][-1]
参考资料:
https://github.com/datawhalechina/team-learning-program/blob/master/LeetCodeClassification/2.%E5%8A%A8%E6%80%81%E8%A7%84%E5%88%92.md