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]
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()
文中参考链接
1)
几种方法 一般为+1
装东西问题 +nums[i]
最大问题或能否装满 为max
最小问题为min
2)
01背包迭代背包时 是倒序的
完全背包迭代背包时 是正序的
排列问题 外层是背包 内层是物品
组合问题 外层是物品 内层是背包
(看物品是否确定顺序,在外层时候是确定好的,是组合问题)
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))
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]
以上股票买卖代码没有可迭代性,所以以上废除,明天按照dp新办法,重新做下以上两题。
看完动态规划要4天了,没事慢慢看吧,计划今年年底之前看完,应该是没有问题