动态规划

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]
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值