力扣刷题-python-动态规划-2 (完全背包、多重背包、打家劫舍、股票买卖)

文章目录

    • 1.完全背包题型
    • 2.多重背包问题
    • 3.背包问题总结
    • 4.打家劫舍问题
    • 5.买卖股票的最佳时期
    • 6.总结

1.完全背包题型

377. 组合总和 Ⅳ - 力扣(LeetCode) (leetcode-cn.com)
物品可以重复取,说明为完全背包问题。完全背包问题,遍历背包时候需要从小到大。
而且取出来物品是有顺序的,说明为排列问题,所以需要外层为背包,内层为物品

class Solution:
    def combinationSum4(self, nums: List[int], target: int) -> int:
        #dp 表示组合的个数
        dp = [0]*(target+1)
        dp[0] = 1 
        #外层是背包 内层是物品
        for j in range(target+1):
            for i in range(len(nums)):
                if j>=nums[i] : dp[j] += dp[j-nums[i]]
        #print(dp)
        return dp[-1]

70. 爬楼梯 - 力扣(LeetCode) (leetcode-cn.com)
爬楼梯问题之前做过,但是不是按照背包问题做的,现在将可以爬的楼梯数1、2转化成数组,也就是背包问题中的物品,,其实也不用数组表示,只需要最大值m即可,因为可以从1遍历到m,就是物品可以取范围
因为物品可以随便取,所以转化成了完全背包问题
顺序确定,即为排列问题,,即需要外层背包,内层物品。

class Solution:
    def climbStairs(self, n: int) -> int:

        #i个台阶 有dp[i]种爬法
        dp=[0]*(n+1)
        m = 2
        dp[0]=1
        for i in range(n+1):  #遍历背包
              for j in  range(1,m+1): #遍历物品
                 if i>=j:  dp[i] += dp[i-j]
        #print(dp)
        return dp[-1]

322. 零钱兑换 - 力扣(LeetCode) (leetcode-cn.com)
其实之前做过零钱兑换,但是里面可取的是有限的,但是这个变成了无限,所以为完全背包问题。
由于要取最小个数情况,也就是递推时候,要取最小值
d[j]= min(d[j],d[j-num[i])
要求初始化时候,将除第一个之外全部初始化为最大值。

class Solution:
    def coinChange(self, coins: List[int], amount: int) -> int:
        #这是个完全背包问题
        #这是个组合问题  所以要物品在外层 背包在内层
        #dp[i]表示为最少的硬币个数
        #dp初始话必须要初始成较大的数
        dp = [amount+1]*(amount+1)
        dp[0]=0
        for i in range(len(coins)):
            for j in range(coins[i],amount+1):    
                dp[j] =min(dp[j], dp[j-coins[i]]+1)
        #print(dp)
        return dp[-1] if dp[-1]<=amount else -1 

最小组合数量用 min,最大组合数量用max,组合数量用sum
279. 完全平方数 - 力扣(LeetCode) (leetcode-cn.com)

class Solution:
    def numSquares(self, n: int) -> int:
        #可以重复取为完全背包  最小数量不管组合和排列问题 所以内外顺序都可以
        dp = [n+1]*(n+1)
        dp[0]=0
        i=1
        squ,i= i*i, i+1       
        while squ<=n:
            for j in range(squ, n+1): 
                 dp[j] = min(dp[j],dp[j-squ]+1)
            squ,i= i*i, i+1  
        #print(dp)
        return dp[-1]

139. 单词拆分 - 力扣(LeetCode) (leetcode-cn.com)
直接用暴力是通过不了的,后加入了记忆法,通过是通过了,但是速度还是比较慢

class Solution:
    def wordBreak(self, s: str, wordDict: List[str]) -> bool:
        def backtrcaking(s,wordDict):
            if not s :return True
            if s in memory: return memory[s]
            for right in range(len(s)):
                if s[:right+1] in wordDict:
                    if backtrcaking(s[right+1:],wordDict):return True
            memory[s]=False
            return False
        memory = collections.defaultdict()    
        return backtrcaking(s,wordDict)

实际这是个完全背包问题,

class Solution:
    def wordBreak(self, s: str, wordDict: List[str]) -> bool:
         #实际是个完全背包问题
         #dp[i] 表示s长度为i时候是否可以被匹配
         #dp[i]=dp[i-wordDICT]
         target= len(s)
         dp = [False]*(target+1)
         dp[0]= True
         for j in range(target+1): #外层背包
              for i in wordDict:   #内层物品
                   if j >= len(i):dp[j] = dp[j] or (dp[j-len(i)] and s[j-len(i):j] ==i)
         #print(dp)
         return dp[-1]

2.多重背包问题

力扣刷题-python-动态规划-2 (完全背包、多重背包、打家劫舍、股票买卖)_第1张图片
下面的代码是我直接复制过来的

def test_multi_pack1():
    '''版本一:改变物品数量为01背包格式'''
    weight = [1, 3, 4]
    value = [15, 20, 30]
    nums = [2, 3, 2]
    bag_weight = 10
    for i in range(len(nums)):
        # 将物品展开数量为1
        while nums[i] > 1:
            weight.append(weight[i])
            value.append(value[i])
            nums[i] -= 1
    
    dp = [0]*(bag_weight + 1)
    # 遍历物品
    for i in range(len(weight)):
        # 遍历背包
        for j in range(bag_weight, weight[i] - 1, -1):
            dp[j] = max(dp[j], dp[j - weight[i]] + value[i])
    
    print(" ".join(map(str, dp)))

def test_multi_pack2():
    '''版本:改变遍历个数'''
    weight = [1, 3, 4]
    value = [15, 20, 30]
    nums = [2, 3, 2]
    bag_weight = 10

    dp = [0]*(bag_weight + 1)
    for i in range(len(weight)):
        for j in range(bag_weight, weight[i] - 1, -1):
            # 以上是01背包,加上遍历个数
            for k in range(1, nums[i] + 1):
                if j - k*weight[i] >= 0:
                    dp[j] = max(dp[j], dp[j - k*weight[i]] + k*value[i])

    print(" ".join(map(str, dp)))


if __name__ == '__main__':
    test_multi_pack1()
    test_multi_pack2()

3.背包问题总结

文中参考链接
1)
几种方法 一般为+1
装东西问题 +nums[i]
最大问题或能否装满 为max
最小问题为min
2)
01背包迭代背包时 是倒序的
完全背包迭代背包时 是正序的

排列问题 外层是背包 内层是物品
组合问题 外层是物品 内层是背包
(看物品是否确定顺序,在外层时候是确定好的,是组合问题)

4.打家劫舍问题

198. 打家劫舍 - 力扣(LeetCode) (leetcode-cn.com),
打家劫舍是经典的dp问题
不能连续取两个值,还要所有取的最大
定义dp[i] 为前i家最大选取和
递推公式为 dp[i] = max(dp[i-2]+num[i],dp[i-1])
因为i是紧挨这i-1 所以取i-1 是不能加num[i]的,因为题目要求最大,只能在两个里面取最大的。
dp数组的大小,应该为家的个数,但是为了减少初值的操作,前端加入一个dp[0]=0
这样dp[i]= max(dp[i-2]+num[i-1],dp[i-1])

class Solution:
    def rob(self, nums: List[int]) -> int:
        dp= [0]*(len(nums)+1)
        for i in range(1,len(nums)+1):
            if i==1: dp[i]=nums[i-1]
            else:dp[i]= max(dp[i-2]+nums[i-1], dp[i-1]) 
        #print(dp)
        return dp[-1] 

213. 打家劫舍 II - 力扣(LeetCode) (leetcode-cn.com)
居然变成环了,头尾相连,那就是考虑两种情况,前n-1 和后n-1,然后求这两种情况最大值。

class Solution:
    def rob(self, nums: List[int]) -> int:
        #只要考虑前n-1 和后n-1 两种情况 然后取两个情况最大值
        def robrange(nums):
            dp = [0]*(len(nums)+1)
            for i in range(1,len(nums)+1):
               if i ==1: dp[i]= nums[i-1]
               else : dp[i] = max(dp[i-2]+ nums[i-1], dp[i-1])
            #print(dp)
            return  dp[-1]

        return max(robrange(nums[:-1]),robrange(nums[1:])) if len(nums)-1 else nums[-1]

337. 打家劫舍 III - 力扣(LeetCode) (leetcode-cn.com)

# Definition for a binary tree node.
# class TreeNode:
#     def __init__(self, val=0, left=None, right=None):
#         self.val = val
#         self.left = left
#         self.right = right
class Solution:
    def rob(self, root: TreeNode) -> int:
        def robtree(noder):
            if not noder:return 0,0
            left = robtree(noder.left)
            right= robtree(noder.right)
            val1= noder.val + left[1] + right[1]  #偷本结点 和不偷孩子节点 但是要把val2迭代回去
            val2= max(left) + max(right)          #不偷本自己  偷孩子节点
            return val1,val2  
        
        return max(robtree(root))

5.买卖股票的最佳时期

121. 买卖股票的最佳时机 - 力扣(LeetCode) (leetcode-cn.com)
可以用贪心算法,先求右侧小值,然后用左侧值不断减去他,最终取大值

class Solution:
    def maxProfit(self, prices: List[int]) -> int:
        #贪心算法  先找左侧的小值,然后用右侧值不断减去小值
        minnum = float(inf)
        res = 0
        for i in prices:
            minnum = min(minnum, i)
            res= max(res, i-minnum)
        return res
class Solution:
    def maxProfit(self, prices: List[int]) -> int:
        #用动态规划
        #dp[i] 第i天卖出最大收益
        #dp[i] = max(prices[i]-prices[i-1]+dp[i-1],dp[i])
        dp = [0]*len(prices)
        for i in range(len(prices)):
            if not i :dp[i]=0
            else: dp[i] = max(prices[i]-prices[i-1]+dp[i-1],dp[i])
        #print(dp)
        return max(dp)

122. 买卖股票的最佳时机 II - 力扣(LeetCode) (leetcode-cn.com)

class Solution:
    def maxProfit(self, prices: List[int]) -> int:
         #动态规划
         #dp[i]  第i天获得最大收益
         #dp[i] = max(prices[i]-prices[i-1]+dp[i-1], dp[i-1])
         dp = [0]*len(prices)
         for i in range(len(prices)):
             if not i :dp[i]=0
             else: dp[i] = max(prices[i]-prices[i-1]+dp[i-1], dp[i-1])
         return dp[-1]

6.总结

以上股票买卖代码没有可迭代性,所以以上废除,明天按照dp新办法,重新做下以上两题。
看完动态规划要4天了,没事慢慢看吧,计划今年年底之前看完,应该是没有问题

你可能感兴趣的:(python(力扣)-刷题笔记,动态规划,leetcode,python)