1.BM79 打家劫舍(二)
题:为了防止被发现,你不能偷相邻的两家,即,如果偷了第一家,就不能再偷第二家,如果偷了第二家,那么就不能偷第一家和第三家。且第一个房间和最后一个房间视为相邻。计算在不被发现的前提下最多的偷窃金额。
#对于一个人家,我们选择偷他或者不偷他,如果我们选择偷那么前一家必定不能偷;如果选择不偷他,那我们最多可以取得上一级的收益
#移方程为dp[i] = max(dp[i - 1], nums[i - 1] + dp[i - 2])
#既然是个环,那么第一家和最后一家不能同时偷。所以要分两种情况。
class Solution:
def rob(self , nums: List[int]) -> int:
dp = [0] * len(nums)
dp[0],dp[1]=nums[0],max(nums[:2])
for i in range(2,len(nums)-1):#情况一:偷了第一家,不能偷最后一家
dp[i]=max(dp[i-1],dp[i-2]+nums[i])
res1=dp[len(nums)-2]#记录情况一这样偷的最大值
dp[0],dp[1]=0,nums[1]
for i in range(2,len(nums)):#情况二:不偷第一家,选择最后一家是否同偷
dp[i]=max(dp[i-1],dp[i-2]+nums[i])
return max(res1,dp[len(nums)-1])
2. 给定两个字符串str1和str2,输出两个字符串的最长公共子序列。如果最长公共子序列为空,则返回"-1"。
#遍历两个字符串的所有位置比较 dp[i][j]表示在s1中以i结尾,s2中以j结尾的字符串的最长公共子序列
#状态转移:
- i位与j位的字符相等,则该问题可以变成dp[i-1][j-1]+s[i]
- 不相同,因此我们考虑,dp[i][j-1]或者dp[i-1][j],取较大的一个
#优化 因为dp[i][j]只和dp[i][j-1]、dp[i-1][j]有关 空间复杂度可再降低
class Solution:
def LCS(self , s1: str, s2: str) -> str:
s1=' '+s1#既防止特例 又不用管第一个和第一个比较
s2=' '+s2
n,m=len(s1),len(s2)
pre=['']*m
for i in range(1,n):
cur=['']*m #相当于dp的行向量每次都初始化
for j in range(1,m):
if s1[i]==s2[j]:
cur[j]=pre[j-1]+s1[i]
else:
if len(pre[j])>len(cur[j-1]):
cur[j]=pre[j]
else:
cur[j]=cur[j-1]
pre=cur #记录上一个行向量dp[i-1][:]
res=cur[-1] if len(cur[-1]) else '-1'
return res
#不优化版
class Solution:
def LCS(self , s1: str, s2: str) -> str:
s1=' '+s1#既防止特例 又不用管第一个和第一个比较
s2=' '+s2
n,m=len(s1),len(s2)
dp=[['']*m for _ in range(n)]
for i in range(1,n):
for j in range(1,m):
if s1[i]==s2[j]:
dp[i][j]=dp[i-1][j-1]+s1[i]
else:
if len(dp[i-1][j])>len(dp[i][j-1]):
dp[i][j]=dp[i-1][j]
else:
dp[i][j]=dp[i][j-1]
res=dp[-1][-1] if len(dp[-1][-1]) else '-1'
return res
3.BM66 最长公共子串
题:给定两个字符串str1和str2,输出两个字符串的最长公共子串。题目保证str1和str2的最长公共子串存在且唯一
注意这题求的是最长公共子串,不是最长公共子序列,子序列可以是不连续的,但子串一定是连续的。
#暴力枚举法
class Solution:
def LCS(self , str1: str, str2: str) -> str:
#让str1为较长的字符串
if len(str1) < len(str2):
str1, str2 = str2, str1
res = ''
max_len = 0
#遍历str1的长度
for i in range(len(str1)):
#查找是否存在 存在的话就一直往后移动 !最大子串唯一
if str1[i - max_len : i + 1] in str2:
res = str1[i - max_len : i + 1]#最大子串
max_len += 1#相同的最大长度
return res
动态规划
#dp[i][j]表示在str1中以第i个字符结尾,str2中以第j个字符结尾,公共子串的长度
#优化版 把dp[i][j]=cur[j] dp[i-1][j]=pre[j]
class Solution:
def LCS(self , str1: str, str2: str) -> str:
str1=' '+str1#既防止特例 又不用管第一个和第一个比较
str2=' '+str2
n,m=len(str1),len(str2)
dp=[[0]*m for _ in range(n)]
ma=0
for i in range(1,n):
for j in range(1,m):
if str1[i]==str2[j]:
dp[i][j]=dp[i-1][j-1]+1
else:
dp[i][j]=0
#更新最大长度 和最大长度所在位置
if dp[i][j]>ma:
ma=dp[i][j]
re=i #在i这个位置结束相同 (此后ma,re没有再更新过)
return str1[re-ma+1:re+1]
#直接判断 注意 子序列不连续,子串是连续的
#遍历str1中的字符 构成子串 判断是否在str2中
class Solution:
def LCS(self, str1, str2):
a,res = '',''
for i in str1:
a = a + i
if a in str2:
res = a
else:
a = a[1:]#子串往后走
return res
4.BM68 矩阵的最小路径和
题:给定一个 n * m 的矩阵 a,从左上角开始每次只能向右或者向下走,最后到达右下角的位置,路径上所有的数字累加起来就是路径和,输出所有的路径中最小的路径和。
#状态转移公式为dp[i][j]=min(dp[i−1][j],dp[i][j−1])+matrix[i][j]
#优化 空间优化 因为状态转移方程不涉及走过的matrix[i][j]的值,因此可以使用matrix代替dp
class Solution:
def minPathSum(self , matrix: List[List[int]]) -> int:
n,m=len(matrix),len(matrix[0])
#dp=matrix
for j in range(1,m):#初始化第一行
matrix[0][j]=matrix[0][j]+matrix[0][j-1]
for i in range(1,n):#初始化第一列
matrix[i][0]=matrix[i][0]+matrix[i-1][0]
for i in range(1,n):#遍历矩阵
for j in range(1,m):
matrix[i][j]=matrix[i][j]+min(matrix[i-1][j],matrix[i][j-1])
return matrix[-1][-1]
5.BM69 把数字翻译成字符串
对于一个数,我们可以直接译码它,也可以将其与前面的组合起来译码
dp[i]=dp[i−1]+dp[i−2]
或 dp[i]=dp[i−2]
class Solution:
def solve(self , nums: str) -> int:
dp=[0]*3
if nums[0] =='0':return 0
dp[0]=1
dp[1]=1
for i in range(1,len(nums)):
if nums[i]!="0":#可以直接译
dp[2]=dp[1]
else:
dp[2]=0
#那到底能和前面那个在一起译么
if 10 <= int(nums[i-1:i+1])<= 26:#可以和前面那个在一起译
dp[2]+=dp[0]
dp[1],dp[0]=dp[2],dp[1]
return dp[1]#不输出dp[2]是为保证n=1时有输出