目录
2020/12/10
70. 爬楼梯
这道简单题 还有几种数学上的快速求解方法 以后再研究 先弄清楚dp的写法
class Solution:
def __init__(self):
self.result=[0,1,2]
def climbStairs(self, n: int) -> int:
# n的结果如果之前计算过 直接取
if(len(self.result)>n):
return self.result[n]
# n的结果之前没有计算过 进行计算
else:
cur=self.climbStairs(n-2)+self.climbStairs(n-1)
# 保存n的结果
self.result.append(cur)
return cur
class Solution:
def climbStairs(self, n: int) -> int:
if(n<=2):
return n
pre1=1
pre2=2
for i in range(3,n+1):
cur=pre1+pre2
pre1=pre2
pre2=cur
return cur
198. 打家劫舍
以5为例 rob(5) 返回nums长度为5时的max结果
- ① 不偷5:robwith5=rob(4)
- ② 偷5:robwithout5=rob(3)+nums[5](第五个数的值 忽略这个index的错误)
- ③rob(5)取上述最大值
class Solution:
def rob(self, nums: List[int]) -> int:
pre1=0
pre2=0 #比pre1的index+1
cur=0
for i in range(0,len(nums)):
robWith=pre2 #偷i
robWithout=pre1+nums[i] #不偷i
cur=max(robWith,robWithout) #当前最大可偷值
#交换
pre1=pre2
pre2=cur
return cur
213. 打家劫舍 II
环状的房屋 要转化成线性形式的list
首尾不能同时偷 说明开始节点和最终节点 必定至少有一个不能偷:
- 不偷开始节点 robLine(1:结尾)
- 不偷最后节点 robLine(0:结尾-1)
自己的思路误区是
- 不偷开始节点的时候就一定要计算必须偷最后节点的最大值 其实不偷开始节点的情况下也不一定必须要偷最后节点
- 相同的 自己认为不偷最后节点就一定要偷开始节点 和上述一样错误
class Solution:
def rob(self, nums: List[int]) -> int:
if(len(nums)==1):
return nums[0]
robLast=self.robLine(nums[1:]) #不偷第一个
robLastno=self.robLine(nums[:-1]) #不偷最后一个节点
return max(robLast,robLastno)
# 偷不成环的nums的最大值
def robLine(self,nums):
pre1,pre2,cur=0,0,0
for i in range(0,len(nums)):
robWith=pre1+nums[i]
robWithout=pre2
cur=max(robWith,robWithout)
pre1=pre2
pre2=cur
return cur
2020/12/12
64. 最小路径和
class Solution:
def minPathSum(self, grid: List[List[int]]) -> int:
m=len(grid) #行数
n=len(grid[0]) #列数
dp=grid
for j in range(1,n):
dp[0][j]=dp[0][j-1]+dp[0][j]
for i in range(1,m):
dp[i][0]=dp[i-1][0]+dp[i][0]
for i in range(1,m):
for j in range(1,n):
dp[i][j]=min(dp[i-1][j],dp[i][j-1])+dp[i][j]
#print(dp)
return dp[m-1][n-1]
注意dp初始化的问题
- dp=[[0]*n]*m 这种写法就是不对 是因为*[m] 导致每一行相等 可是[0]*m为啥没让每一列相等呢???
- dp=[[0]*n for _ in range(m)] 这种写法对的
class Solution:
def minPathSum(self, grid: List[List[int]]) -> int:
m=len(grid) #行数
n=len(grid[0]) #列数
dp=[[0]*n for _ in range(m)]
#dp=[[0]*n]*m 这种写法就是不对
dp[0][0]=grid[0][0]
for j in range(1,n):
dp[0][j]=dp[0][j-1]+grid[0][j]
for i in range(1,m):
dp[i][0]=dp[i-1][0]+grid[i][0]
for i in range(1,m):
for j in range(1,n):
dp[i][j]=min(dp[i-1][j],dp[i][j-1])+grid[i][j]
#print(dp)
return dp[m-1][n-1]
62. 不同路径
还有数学思路 先不研究
class Solution:
def uniquePaths(self, m: int, n: int) -> int:
dp=[[1]*n for _ in range(m)]
for i in range(1,m):
for j in range(1,n):
dp[i][j]=dp[i-1][j]+dp[i][j-1]
return dp[m-1][n-1]
dp和递归的区别 是递归没有记忆 dp把之前的记下来 所以递归会多算(有的题目fn 只和fn-1相关 不会多算)
class Solution:
def uniquePaths(self, m: int, n: int) -> int:
if(m<1 or n<1):
return 0
if(m==1 or n==1):
return 1
return self.uniquePaths(m-1,n)+self.uniquePaths(m,n-1)
303. 区域和检索 - 数组不可变
计算一个累计数组
0,nums[1], nums[1]+nums[2]
直接return self.sumNum[j+1]-self.sumNum[i]
前一项是累加到j 后一项是累加到i-1 这里能看出累加数组开头多一个0元素的好处
class NumArray:
def __init__(self, nums: List[int]):
if(nums==None or len(nums)==0):
self.sumNum=[]
else:
self.sumNum=[0]*(len(nums)+1)#这个数组表示 nums从0到index-1的累加结果
# 0 nums[1] nums[1]+nums[2] ....
for i in range(1,len(nums)+1):
self.sumNum[i]=self.sumNum[i-1]+nums[i-1]
def sumRange(self, i: int, j: int) -> int:
if(self.sumNum==[]):
return None
return self.sumNum[j+1]-self.sumNum[i]
413. 等差数列划分
自己思路:直接遍历 只要连续dffer相同 则differ不同时进行更新result
class Solution:
def numberOfArithmeticSlices(self, A: List[int]) -> int:
if(len(A)<3):
return 0
result=0
pre=A[1]-A[0] # 连续两元素之差
curDiffer=1 # 当前等差的元素数
for i in range(2,len(A)):
cur=A[i]-A[i-1] # 当前的差
# 当前差和上一个差相同
if(cur==pre):
curDiffer+=1
if(cur!=pre or i==len(A)-1):#直接用else 忽略了整个A都是等差的情况
# 先更新result
if(curDiffer>1):
result+=(curDiffer*(curDiffer-1)/2) # 1+2+3...+(curDiffer-1)
# 再更新curDiffer=1 以及pre
curDiffer=1
pre=cur
return int(result)
- dp一维数组表示以每个元素为结尾的等差数组数量
- 只有[i]和[i-1]的差 等于之前等差的差时 才更新dp[i]
- 情况1:dp[i-1]都以[i]为结尾 dp[i-1]个
- 情况2:[i-2] [i-1] [i] 1个
- 最后相加dp
class Solution:
def numberOfArithmeticSlices(self, A: List[int]) -> int:
if(len(A)<3):
return 0
dp=[0]*len(A) # 每个index代表 以[此index元素作为等差数列结尾] 的个数
for i in range(2,len(A)):
if(A[i]-A[i-1]==A[i-1]-A[i-2]):
dp[i]=dp[i-1]+1 # 情况1:dp[i-1]都以[i]为结尾;情况2:[i-2] [i-1] [i] 新的等差
return sum(dp)
343. 整数拆分
自己的思路:
- 一个数从所有可能相加组合中选最大结果
- left从1到num/2 right=num-left
- 每个结果取最大 [left,dp[left]] ×[right,dp[right]]
class Solution:
def integerBreak(self, n: int) -> int:
dp=[0]*(n+1)
dp[1]=1
for i in range(2,n+1):
curMax=0
for left in range(1,int(i/2)+1):
# left*right left*dp[right] dp[left]*dp[right] dp[left]*right 选最大
curMax=max(curMax,max(left,dp[left])*max(i-left,dp[i-left]))
dp[i]=curMax
return dp[n]
官方给的是这样:
- 一个数从所有可能相加组合中选最大结果
- left从1到num-1 right=num-left
- 每个结果 取最大 [left] × [right,dp[right]]
class Solution:
def integerBreak(self, n: int) -> int:
dp=[0]*(n+1)
dp[1]=1
for i in range(2,n+1):
curMax=0
# for left in range(1,int(i/2)+1):
# # left可以选择left或dp[left]; right可以选择right或dp[right] 选最大
# curMax=max(curMax,max(left,dp[left])*max(i-left,dp[i-left]))
for left in range(1,i): # 官方
curMax=max(curMax,left*max(i-left,dp[i-left])) #官方
dp[i]=curMax
return dp[n]
其实我这种写法比官方多了 dp[left] * dp[right] 操作按官方的思路 不用判断这种情况 我还在想为什么??可能是因为其它的判断中已经包含了这种情况
比如 n=10 我的判断过程是:
- [1,dp[1]],[9,dp[9]]
- [2,dp[2]],[8,dp[8]]
- [3,dp[3]],[7,dp[7]]
- [4,dp[4]],[6,dp[6]]
- [5,dp[5]],[5,dp[5]]
官方的过程是:
- [1],[9,dp[9]]
- [2],[8,dp[8]]
- [3],[7,dp[7]]
- [4],[6,dp[6]]
- [5],[5,dp[5]]
- [6],[4,dp[4]]
- [7],[3,dp[3]]
- [8],[2,dp[2]]
- [9],[1,dp[1]]
比如 以4 6为例 我的多考虑了 dp[4]*dp[6] 加入拆成 22 33 不就相当于2 *dp[8]了吗 会判断到的 拆成13 33 不就相当于dp[1]*dp[9] 也会判断到 因此可以把自己写的改成和官方一样的 只需要去掉这种比较
class Solution:
def integerBreak(self, n: int) -> int:
dp=[0]*(n+1)
dp[1]=1
for i in range(2,n+1):
curMax=0
# for left in range(1,int(i/2)+1):
# # left可以选择left或dp[left]; right可以选择right或dp[right] 选最大
# curMax=max(curMax,max(left,dp[left])*max(i-left,dp[i-left]))
# for left in range(1,i): # 官方
# curMax=max(curMax,left*max(i-left,dp[i-left])) #官方
for left in range(1,int(i/2)+1):
#完全等同于官方
curMax=max(curMax,left*(i-left),left*dp[i-left],dp[left]*(i-left))
dp[i]=curMax
return dp[n]
2020/12/13
279. 完全平方数
这题一直看在超出时间限制上了。。 所以用了集合来存储所有的平方根数
- 如果n是 根号的数 直接返回n
- 不是的话,最差情况return n,但是left+right=n 只要left或right是根数 就可以更新dp[n]
class Solution:
def numSquares(self, n: int) -> int:
if(n<2):
return 1
dp=[1]*(n+1)
sqrtNum=[]
for i in range(2,n+1):
if(self.isSqrt(i)):
sqrtNum.append(i)
else:
curMin=i
# 所有可能相加组合中选最小的
# for left in range(1,int(i/2)+1):#不加1 会忽略掉 6-6情况(12为例)
# if(dp[left]==1 or dp[i-left]==1): # 当 left-right中有平方根时 更新
# curMin=min(curMin,dp[left]+dp[i-left])
for num in sqrtNum:
curMin=min(curMin,1+dp[i-num])
dp[i]=curMin
return dp[n]
def isSqrt(self,n):
n_sqrt=int(n**0.5)
if(n_sqrt**2==n):
return True
else:
return False
还可以直接求sqrt_num
class Solution:
def numSquares(self, n: int) -> int:
dp=[1]*(n+1)
sqrtNum=[i**2 for i in range(1,int(n**0.5)+1)] # 所有小于等于n的平方数
for i in range(1,n+1):
# 所有可能相加组合中选最小的
if(i not in sqrtNum):
curMin=i
for num in sqrtNum:
if(num<i):
curMin=min(curMin,1+dp[i-num])
dp[i]=curMin
return dp[n]
2020/12/14
91. 解码方法
class Solution:
def numDecodings(self, s: str) -> int:
dp=[1]*(len(s)+1)
s='0'+s
for i in range(1,len(s)):
# 要判断 i-1 的值
if(s[i-1]=='0'):
if(s[i]=='0'):
return 0
else:
dp[i]=dp[i-1]
elif(s[i-1]=='1'):
if(s[i]=='0'):
dp[i]=dp[i-2]
else:
dp[i]=dp[i-2]+dp[i-1]
elif(s[i-1]=='2'):
if(s[i]=='0'):
dp[i]=dp[i-2]
else:
if(s[i]>='1' and s[i]<='6'):
dp[i]=dp[i-1]+dp[i-2]
else:
dp[i]=dp[i-1]
else:
if(s[i]=='0'):
return 0
else:
dp[i]=dp[i-1]
#print(dp)
return dp[-1]
300. 最长上升子序列
o(n^2)的思路
class Solution:
def lengthOfLIS(self, nums: List[int]) -> int:
if(len(nums)<2):
return len(nums) # 0 / 1
dp=[1]*len(nums)
for i in range(1,len(nums)):
# i 要和 0~i-1的比 选出最大
for j in range(0,i):
if(nums[i]>nums[j]):
dp[i]=max(dp[i],dp[j]+1)
#print(dp)
return max(dp)
改进 其实就是找[i] 能大于的最大的数j 然后dp[i]=dp[j]+1
竟然卡在了二分查找 找大于等于target的第一个元素的Index上
找第一个大于等于target的元素 那么Mid小于target时候 left就得=mid+1
class Solution:
def lengthOfLIS(self, nums: List[int]) -> int:
if(len(nums)<2):
return len(nums) # 0 / 1
dp=[]# dp[i]表示长度为(i+1)的上升子序列末尾元素的最小值
for num in nums:
# 1 num大于dp中的所有值或者dp无元素
if(len(dp)==0 or num>dp[-1]):
dp.append(num)
#print(dp)
# 2 dp[]中有大于等于num的 找到第一个的Index
else:
# 二分查找
left=0
right=len(dp)-1
mid=0
# 找第一个大于等于target的元素 那么Mid小于target时候 left就得=mid+1
while(left<right):
mid=left+int((right-left)/2)
if(dp[mid]>=num):
right=mid
else:
left=mid+1
dp[left]=num #如果这要用mid 就得改成left<=right 就会死循环
#print(dp)
return len(dp)
2020/12/26
646. 最长数对链
记得先排序 再dp 不知道为啥就对了。。
class Solution:
def findLongestChain(self, pairs: List[List[int]]) -> int:
if(len(pairs)<=1):
return len(pairs)
pairs.sort(key=lambda x:(x[0],x[1]))
dp=[] # dp[i]表示长度为i+1的对链 的最后数对 的尾元素
for pair in pairs:
# 1 pair队首大于dp[-1]
if(len(dp)==0 or pair[0]>dp[-1]):
dp.append(pair[1])
# 2 dp[]中有大于pair的位置
# dp[i]>pair尾 and dp[i-1]<pair头的情况下可以换掉dp[i]
else:
# 2-1 先找大于pair尾的dp元素index
left=0
right=len(dp)-1
while(left<right):
mid=left+(right-left)//2
if(dp[mid]<=pair[1]):
left=mid+1
else:
right=mid
# 找到对应的Index为left
if(left==0 and dp[left]>pair[1]):
dp[left]=pair[1]
if(dp[left]>pair[1] and left>0 and dp[left-1]<pair[0]):
dp[left]=pair[1]
return len(dp)
好像是贪心可以做,dp也可以做
https://leetcode-cn.com/problems/maximum-length-of-pair-chain/solution/chuan-shang-yi-fu-wo-jiu-bu-ren-shi-ni-liao-lai–2/
2021/1/8
376. 摆动序列
自己写半天 有点乱
class Solution:
def wiggleMaxLength(self, nums: List[int]) -> int:
if(len(nums)<2):
return len(nums)
# dp[i]表示以index为i的元素 结尾 最长的数量
dp=[1]*len(nums)
pre=0 #前一个差
for i in range(1,len(nums)):
cur=nums[i]-nums[i-1]
if(cur>0):
cur=1
elif(cur<0):
cur=-1
# cur= 1 -1 0
# 1:cur和pre异号
# 2:cur和pre同号
if(pre==0):
if(cur==0):
dp[i]=dp[i-1]
else:
dp[i]=dp[i-1]+1
else:
# pre!=0
if(cur+pre==0):
dp[i]=dp[i-1]+1
else:
dp[i]=dp[i-1]
if(cur!=0):
pre=cur
return max(dp)
看官方的解答:
- up[i] 表示以前 i 个元素中的某一个为结尾的最长的「上升摆动序列」的长度。
- down[i] 表示以前 i 个元素中的某一个为结尾的最长的「下降摆动序列」的长度。
链接:https://leetcode-cn.com/problems/wiggle-subsequence/solution/bai-dong-xu-lie-by-leetcode-solution-yh2m/
class Solution:
def wiggleMaxLength(self, nums: List[int]) -> int:
if(len(nums)<2):
return len(nums)
# dp[i]表示前i个元素中最长序列长度
up=[1]*len(nums)
down=[1]*len(nums)
for i in range(1,len(nums)):
# 只有此元素大于上一个元素
if(nums[i]>nums[i-1]):
up[i]=max(down[i-1]+1,up[i-1])
elif(nums[i]<nums[i-1]):
down[i]=max(up[i-1]+1,down[i-1])
else:
up[i]=up[i-1]
down[i]=down[i-1]
return max(up[-1],down[-1])
2021/1/10
1143. 最长公共子序列
这题太绝了,怎么也想不到,看答案和走路径那种差不多,关键是dp是个二维的矩阵。
class Solution:
def longestCommonSubsequence(self, text1: str, text2: str) -> int:
row,col=len(text1),len(text2)
if(row==0 or col==0):
return 0
# dp[i][j] 表示text1[:i]和text2[:j] 的最长公共子序列长度
dp=[[0]*col for _ in range(0,row)]
# row=0时 text2元素出现了text1[0] 后面位置的dp[i][j]=1
for j in range(0,col):
# dp[i][j] 默认为0
if(text2[j]==text1[0]):
dp[0][j]=1
else:
dp[0][j]=dp[0][j-1] if j>0 else 0
for i in range(0,row):
if(text1[i]==text2[0]):
dp[i][0]=1
else:
dp[i][0]=dp[i-1][0] if i>0 else 0
for i in range(1,row):
for j in range(1,col):
if(text2[j]==text1[i]):
dp[i][j]=dp[i-1][j-1]+1
else:
dp[i][j]=max(dp[i-1][j],dp[i][j-1])
# print(dp)
return dp[row-1][col-1]
刚开始初始化了第一行 第一列 但是初始化dp时 长度+1 就可以避免初始化第一行、列的代码。。
class Solution:
def longestCommonSubsequence(self, text1: str, text2: str) -> int:
row,col=len(text1),len(text2)
if(row==0 or col==0):
return 0
# dp[i][j] 表示text1[:i-1]和text2[:j-1] 的最长公共子序列长度
dp=[[0]*(col+1) for _ in range(0,row+1)]
for i in range(1,row+1):
for j in range(1,col+1):
if(text2[j-1]==text1[i-1]):
dp[i][j]=dp[i-1][j-1]+1
else:
dp[i][j]=max(dp[i-1][j],dp[i][j-1])
# print(dp)
return dp[row][col]