1 线性动态规划
1.1 单串
T1 最长递增子序列
class Solution(object):
def lengthOfLIS(self, nums):
"""
:type nums: List[int]
:rtype: int
"""
l=len(nums)
dp=[0]*l
for i in range(l):
if i==0:
dp[i]=1
else:
for x in range(i):
temp=0
if nums[x]<nums[i]:
temp=dp[x]+1
#dp[i]=max(dp[i],dp[x]+1)
else:
temp=1
#dp[i]=max(dp[i],1)
if temp>dp[i]:
dp[i]=temp
return max(dp)
T2 最长递增子序列的个数
#错误尝试
class Solution(object):
def findNumberOfLIS(self, nums):
"""
:type nums: List[int]
:rtype: int
"""
l=len(nums)
dp=[0]*l
cnt=0 #这个也是要符合dp的呀,没法就用一个值处理掉的
for i in range(l):
if i==0:
dp[i]=1
else:
for x in range(i):
temp=0
if nums[x]<nums[i]:
temp=dp[x]+1
else:
temp=1
if temp>dp[i]:
dp[i]=temp
m=max(dp)
for i in range(l):
if dp[i]==m:
for x in range(i):
if dp[x]==m-1:
cnt+=1
if cnt==0:
cnt=l
return cnt
#正确做法(保守)
class Solution(object):
def findNumberOfLIS(self, nums):
"""
:type nums: List[int]
:rtype: int
"""
l=len(nums)
dp=[0]*l
cnt=[1]*l
for i in range(l):
if i==0:
dp[i]=1
else:
for x in range(i):
if nums[x]<nums[i]:
temp=dp[x]+1
else:
temp=1
if temp>dp[i]:
dp[i]=temp
#这里开启了第二次循环,实际是为了防止dp[i]没有处理到最终,没敢加到过程里
for x in range(i):
if nums[x]<nums[i] and dp[x]==dp[i]-1:
cnt[i]+=cnt[x]
if cnt[i]!=1:
cnt[i]-=1
m=max(dp)
c=0
for i in range(l):
if dp[i]==m:
c+=cnt[i]
return c
#正确答案(稍微简化版)
class Solution(object):
def findNumberOfLIS(self, nums):
"""
:type nums: List[int]
:rtype: int
"""
l=len(nums)
dp=[0]*l
cnt=[1]*l
for i in range(l):
if i==0:
dp[i]=1
else:
for x in range(i):
if nums[x]<nums[i]:
temp=dp[x]+1
if temp>dp[i]:
dp[i]=temp
cnt[i]=cnt[x] #1
#受到答案启发,只要dp[i]还在变,cnt[i]也就直接受到影响变化,因此最终停留的初始状态一定是dp[i]成熟了对应的情况,因而不存在上面的那个担忧
elif temp==dp[i]:
cnt[i]+=cnt[x] #2
else:
temp=1
if temp>dp[i]:
dp[i]=temp
m=max(dp)
c=0
for i in range(l):
if dp[i]==m:
c+=cnt[i]
return c
#再简化一点,即答案的写法
class Solution(object):
def findNumberOfLIS(self, nums):
"""
:type nums: List[int]
:rtype: int
"""
l=len(nums)
dp=[1]*l #区别在这里其实就是少了对于i==0的那些操作了
cnt=[1]*l
if l<=1:
return l
for i in range(1,l):
for x in range(i):
if nums[x]<nums[i]:
temp=dp[x]+1
if temp>dp[i]:
dp[i]=temp
cnt[i]=cnt[x] #1
elif temp==dp[i]:
cnt[i]+=cnt[x] #2
m=max(dp)
c=0
for i in range(l):
if dp[i]==m:
c+=cnt[i]
return c
T3 最大子数组和
class Solution(object):
def maxSubArray(self, nums):
"""
:type nums: List[int]
:rtype: int
"""
l=len(nums)
if l==0:
return 0
dp=[0]*l
for i in range(l):
if i==0:
dp[i]=nums[i]
else:
#dp[i]=max(dp[i-1]+nums[i],nums[i])
if dp[i-1]>0:
dp[i]=dp[i-1]+nums[i]
else:
dp[i]=nums[i]
return max(dp)
T4 乘积最大子数组
我想到了不符合最优子结构,想到了要区分正负进行不同操作,但是没有想到可以通过储存两个,一个max一个min来处理并且是正确的。其实问题就在于,动归不考虑后面的未知,而是把子问题截断在当下,把每一个当下当作结尾,我想不出来是因为我考虑到,假设此刻是个正数,如果后面还存在负数,那我此刻完全可以选择前面那个负的大的(当然这样还要去比较和全正哪个大),但是如果后面只存在正数,就必须选正的,这样一来就会非常的复杂,摸不着头脑。但是我之所以会这样是因为,我发现了它不符合最优子结构就不知道怎么办了,虽然我知道还是要动归,但是不知道该改哪里,所以就不小心跳出了动归的本质。但动归本质是一定要截断在当下的,不会去考虑当下以后的未知。所以如果截断在当下,以当下为末尾,那么如果当下为负,就去乘上一个的最小得到最大,如果当下为正,就去乘上一个的最大得到最大,为了符合结构一致性,也要用类似的方法维护每个当下得到的最,这样就能得到正规的思路了。
class Solution(object):
def maxProduct(self, nums):
"""
:type nums: List[int]
:rtype: int
"""
l=len(nums)
maxdp=[0]*l
mindp=[0]*l
if l==0:
return
for i in range(l):
if i==0:
maxdp[i]=nums[i]
mindp[i]=nums[i]
else:
if nums[i]<=0:
if mindp[i-1]<=0:
maxdp[i]=mindp[i-1]*nums[i]
else:
maxdp[i]=nums[i]
if maxdp[i-1]<=0:
mindp[i]=nums[i]
else:
mindp[i]=maxdp[i-1]*nums[i]
else: #nums[i]>0
if maxdp[i-1]<=0:
maxdp[i]=nums[i]
else:
maxdp[i]=maxdp[i-1]*nums[i]
if mindp[i-1]<=0:
mindp[i]=mindp[i-1]*nums[i]
else:
mindp[i]=nums[i]
return max(maxdp)
T5 环形子数组的最大和
【法一】暴力×动归
选择的方法是,对于外圈进行暴力迭代,对于针对的每一个值,取出其对应的数组并进行动归。逻辑是正确的,但是对于最大的测试点会超时。
#逻辑正确,但效率有点低,对于最长的测试点会超时
class Solution(object):
def helper(self,nums):
l=len(nums)
dp=[0]*l
for i in range(l):
if i==0:
dp[i]=nums[i]
else:
if dp[i-1]>0:
dp[i]=dp[i-1]+nums[i]
else:
dp[i]=nums[i]
return dp[-1]
def maxSubarraySumCircular(self, nums):
"""
:type nums: List[int]
:rtype: int
"""
l=len(nums)
final=[0]*l
for i in range(l):
a=[]
for j in range(1,l+1):
a.append(nums[(i+j)%l])
final[i]=self.helper(a)
return max(final)
失败的简化尝试:
(试图变成动归×动归,但是事实上本题的外圈不能随随便便动归)
试图通过这种方式进行一个简化,但是并不对,因为它和普通最大子序和的区别不仅仅是初始的求,还在于每一次都要限制长度,如果只用helper算出初始值,然后就递推下去,是不对的,因为这样实际子串长度就超过l了,所以实际上,这个从大问题上来看也是不符合最优子结构的。
#错误的
class Solution(object):
def helper(self,nums):
l=len(nums)
dp=[0]*l
for i in range(l):
if i==0:
dp[i]=nums[i]
else:
if dp[i-1]>0:
dp[i]=dp[i-1]+nums[i]
else:
dp[i]=nums[i]
return dp[-1]
def maxSubarraySumCircular(self, nums):
"""
:type nums: List[int]
:rtype: int
"""
l=len(nums)
final=[0]*l
for i in range(l):
if i==0:
a=[]
for j in range(1,l+1):
a.append(nums[(i+j)%l])
final[i]=self.helper(a)
else:
if final[i-1]>0:
final[i]=final[i-1]+nums[i]
else:
final[i]=nums[i]
return max(final)
【法二】分情况/运用反面/min.max
思路是这样的,因为环形嘛,所以就分为两种情况,一种是最大子数组恰好是连着的那部分;另一种是最大子数组是断开分为两半的,而其对立面(即不取的那部分)恰好是连着的,就可以考虑使用反面,用整体减去连续的最小子数组。正确答案就是这两者其中最大的那个。不过有一个非常关键易被忽略的边界问题就是,用总体去减min的时候存在全都剪掉(就是因为所有的元素都是负的,那肯定是一个都不取得到的值最大)的可能,也就是取出来的是空数组,但题目要求了是非空数组,所以必须避免这种情况,所以在全为负的情况下,返回最大值就可。
class Solution(object):
def maxSubarraySumCircular(self, nums):
l=len(nums)
if l==0:
return
if l==1:
return nums[0]
maxdp=[0]*l
mindp=[0]*l
s=0
for i in range(l):
s+=nums[i]
if i==0:
maxdp[i]=nums[0]
mindp[i]=nums[0]
else:
if maxdp[i-1]>0:
maxdp[i]=maxdp[i-1]+nums[i]
else:
maxdp[i]=nums[i]
if mindp[i-1]<0:
mindp[i]=mindp[i-1]+nums[i]
else:
mindp[i]=nums[i]
a=max(maxdp)
b=s-min(mindp)
#判断是否全部非负
if a<0:
return a
return max(a,b)
T6 最大子矩阵
首先进行了错误的尝试,认为它可以动归乘以动归,也就是两个独立的符合动归的问题,但这个主要问题是,不能对齐,每一行取到的可能并不对齐,违背了矩阵的要求。然后我想到了,要对齐,就可以使用加和的那种,还是两个动归,但是仔细想了一下,加和的话,外面那层就不符合最优子结构了。所以综上,照这个思路还是只能递归×动归(也就是首先列举n²也就是所有可能的加和,变成N²×M矩阵,然后再逐行动归,后半部分也就是和第一次的尝试基本完全相同的),但是个人感觉这个一定会超时,而且好像用前缀和解决会更巧妙一点?还没学,暂且搁置……
#第一次的,错误的尝试
class Solution(object):
def helper(self,nums):
l=len(nums)
if l==0:
return
dp=[0]*l
x1=[0]*l
for i in range(l):
if i==0:
dp[i]=nums[0]
else:
if dp[i-1]>0:
dp[i]=dp[i-1]+nums[i]
x1[i]=x1[i-1]
else:
dp[i]=nums[i]
x1[i]=i
mx2=dp.index(max(dp))
mx1=x1[mx2]
return max(dp),mx1,mx2
def getMaxMatrix(self, matrix):
"""
:type matrix: List[List[int]]
:rtype: List[int]
"""
l1=len(matrix)
if l1==0:
return []
l2=len(matrix[0])
dp=[0]*l1
y1=[0]*l1
for i in range(l1):
if i==0:
dp[i]=self.helper(matrix[0])[0]
else:
temp=self.helper(matrix[i])[0]
if dp[i-1]>0:
dp[i]=dp[i-1]+temp
y1[i]=y1[i-1]
else:
dp[i]=temp
y1[i]=i
my2=dp.index(max(dp))
my1=y1[my2]
mx1_=self.helper(matrix[my1])[1]
mx2_=self.helper(matrix[my1])[2]
mx1__=self.helper(matrix[my2])[1]
mx2__=self.helper(matrix[my2])[2]
mx1=min(mx1_,mx1__)
mx2=max(mx2_,mx2__)
return [my1,mx1,my2,mx2]
T7 打家劫舍Ⅰ
class Solution(object):
def rob(self, nums):
"""
:type nums: List[int]
:rtype: int
"""
l=len(nums)
if l==0:
return 0
if l==1:
return nums[0]
if l==2:
return max(nums)
dp=[0]*l
for i in range(l):
if i==0:
dp[i]=nums[0]
elif i==1:
dp[i]=max(dp[i-1],nums[i])
else:
dp[i]=max(dp[i-1],nums[i]+dp[i-2])
return max(dp)
T8 打家劫舍Ⅱ
class Solution(object):
def rob(self, nums):
"""
:type nums: List[int]
:rtype: int
"""
l=len(nums)
if l==0:
return 0
if l==1:
return nums[0]
if l==2:
return max(nums)
dp1=[0]*(l-1)
dp2=[0]*(l-1)
for i in range(l-1):
if i==0:
dp1[0]=nums[0]
dp2[0]=nums[1]
else:
dp1[i]=max(dp1[i-1],dp1[i-2]+nums[i])
dp2[i]=max(dp2[i-1],dp2[i-2]+nums[i+1])
return max(max(dp1),max(dp2))
1.2 双串
T1 最长公共子序列
class Solution(object):
def longestCommonSubsequence(self, text1, text2):
"""
:type text1: str
:type text2: str
:rtype: int
"""
l1=len(text1)
l2=len(text2)
dp=[[0]*(l2+1) for i in range(l1+1)]
for i in range(1,l1+1):
for j in range(1,l2+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[l1][l2]
T2 编辑距离
# 错了
class Solution(object):
def minDistance(self, word1, word2):
"""
:type word1: str
:type word2: str
:rtype: int
"""
l1=len(word1)
l2=len(word2)
dp=[[0]*(l2+1) for a in range(l1+1)]
dic={}
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]+1
temp=dp[i][j]
if temp not in dic:
l=i-j
if l>=0:
dic[temp]=l
else:
dic[temp]=-l
else:
dp[i][j]=max(dp[i-1][j],dp[i][j-1])
ops=0
for x in dic.keys():
ops+=dic[x]
return dic
#对了
class Solution(object):
def minDistance(self, word1, word2):
"""
:type word1: str
:type word2: str
:rtype: int
"""
l1=len(word1)
l2=len(word2)
dp=[[0]*(l2+1) for a in range(l1+1)]
for i in range(l1+1):
dp[i][0]=i
for j in range(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]=min(dp[i-1][j],dp[i][j-1],dp[i-1][j-1])+1
return dp[l1][l2]
T3 最小路径和
#一遍√
class Solution(object):
def minPathSum(self, grid):
"""
:type grid: List[List[int]]
:rtype: int
"""
l1=len(grid)
l2=len(grid[0])
dp=[[0]*(l2+1) for i in range(l1+1)]
for i in range(l1+1):
dp[i][0]=float('inf')
for j in range(l2+1):
dp[0][j]=float('inf')
for i in range(1,l1+1):
for j in range(1,l2+1):
if i==1 and j==1:
dp[i][j]=grid[i-1][j-1]
else:
dp[i][j]=grid[i-1][j-1]+min(dp[i-1][j],dp[i][j-1])
return dp[l1][l2]
2 前缀和
T1 区域和检索 - 数组不可变
#1 暴力迭代×动归(超时)
class NumArray(object):
def __init__(self, nums):
"""
:type nums: List[int]
"""
self.l=len(nums)
self.n=nums
def sumRange(self, left, right):
"""
:type left: int
:type right: int
:rtype: int
"""
dp=[[0]*self.l for x in range(self.l)]
for i in range(self.l):
for j in range(i,self.l):
if i==j:
dp[i][j]=self.n[j]
else:
dp[i][j]=self.n[j]+dp[i][j-1]
return dp[left][right]
#2 前缀和动归
class NumArray(object):
def __init__(self, nums):
"""
:type nums: List[int]
"""
self.l=len(nums)
self.n=nums
def sumRange(self, left, right):
"""
:type left: int
:type right: int
:rtype: int
"""
dp=[0]*self.l
for i in range(self.l):
if i==0:
dp[i]=self.n[i]
else:
dp[i]=dp[i-1]+self.n[i]
if left>=1:
ret=dp[right]-dp[left-1]
else:
ret=dp[right]
return ret
T2 二维区域和检索 - 矩阵不可变
#1 纯暴力【超时】
class NumMatrix(object):
def __init__(self, matrix):
"""
:type matrix: List[List[int]]
"""
self.m=matrix
def sumRegion(self, row1, col1, row2, col2):
"""
:type row1: int
:type col1: int
:type row2: int
:type col2: int
:rtype: int
"""
s=0
for i in range(row1,row2+1):
s+=sum(self.m[i][col1:col2+1])
return s
# Your NumMatrix object will be instantiated and called as such:
# obj = NumMatrix(matrix)
# param_1 = obj.sumRegion(row1,col1,row2,col2)
返回该题
#2 一维前缀和×暴力【超时】
class NumMatrix(object):
def __init__(self, matrix):
"""
:type matrix: List[List[int]]
"""
self.m=matrix
def sumRegion(self, row1, col1, row2, col2):
"""
:type row1: int
:type col1: int
:type row2: int
:type col2: int
:rtype: int
"""
l1=len(self.m)
l2=len(self.m[0])
dp=[[0]*l2 for i in range(l1)]
for i in range(l1):
for j in range(l2):
if j==0:
dp[i][j]=self.m[i][j]
else:
dp[i][j]=dp[i][j-1]+self.m[i][j]
s=0
for x in range(row1,row2+1):
if col1>=1:
temp=dp[x][col2]-dp[x][col1-1]
else:
temp=dp[x][col2]
s+=temp
return s
# Your NumMatrix object will be instantiated and called as such:
# obj = NumMatrix(matrix)
# param_1 = obj.sumRegion(row1,col1,row2,col2)
#二维前缀和×暴力(还是超时了。。我不理解)
class NumMatrix(object):
def __init__(self, matrix):
"""
:type matrix: List[List[int]]
"""
self.m=matrix
def sumRegion(self, row1, col1, row2, col2):
"""
:type row1: int
:type col1: int
:type row2: int
:type col2: int
:rtype: int
"""
l1=len(self.m)
l2=len(self.m[0])
dp=[[0]*(l2+1) for x in range(l1+1)]
for i in range(1,l1+1):
for j in range(1,l2+1):
if i==1 and j==1:
dp[i][j]=self.m[i-1][j-1]
else:
dp[i][j]=dp[i-1][j]+dp[i][j-1]-dp[i-1][j-1]+self.m[i-1][j-1]
return dp[row2+1][col2+1]-dp[row2+1][col1]-dp[row1][col2+1]+dp[row1][col1]
# Your NumMatrix object will be instantiated and called as such:
# obj = NumMatrix(matrix)
# param_1 = obj.sumRegion(row1,col1,row2,col2)
3 区间动态规划
#错了(循环选取不对)
class Solution(object):
def longestPalindrome(self, s):
"""
:type s: str
:rtype: str
"""
l=len(s)
if l==0:
return
if l==1:
return s
if l==2:
if s[0]==s[1]:
return s
else:
return
dp=[[False]*l for i in range(l)]
for i in range(l):
for j in range(i,l):
if s[i]!=s[j]:
dp[i][j]=False
else:
if j-i<=2:
dp[i][j]=True
else:
dp[i][j]=dp[i+1][j-1]
#未完,但是这里已经犯了一个很明显的错误,就是在i的时候是不知道i+1的,但是这个遍历顺序又是,i只会经历一次,因此根本就是无效的,所以不仅仅状态转移方程重要,遍历顺序也很重要,这就体现出答案那种遍历的优越之处了,最外层遍历是区间长度,内层循环才是i,这样就符合了他那个状态推导的顺序,它一定是从比较短的推到比较长的,所以最外层循环选择从小到大的长度才是正确的。
#正确
class Solution(object):
def longestPalindrome(self, s):
"""
:type s: str
:rtype: str
"""
#前面这些是为了处理一些总长度l比较短的情况
l=len(s)
if l==0:
return
if l==1:
return s
if l==2:
if s[0]==s[1]:
return s
else:
return s[0]
dp=[[False]*l for i in range(l)]
maxlen=1
for thelen in range(1,l+1):
#这里一定记得是从1开始,前面那些特殊情况并没有包含它,因为这是可以在巨长的字符串里获得一个仅自己的回文串
for i in range(l):
j=i+thelen-1
if j>l-1:#这里千万别不小心错写成>=,还是可以到达最长的
break
if s[i]!=s[j]:
dp[i][j]=False
else:
if j-i<=2:
dp[i][j]=True
else:
dp[i][j]=dp[i+1][j-1]
if dp[i][j] and j-i+1>=maxlen:
maxlen=j-i+1
begin=i
return s[begin:begin+maxlen]
后续有时间会继续做区间动归和背包动归相关题目。