leetcode 动态规划——01背包问题系列

 0-1背包问题——二维dp解决方案

import sys
input = sys.stdin.read
data = input().split()
index = 0
M = int(data[index])
index += 1
N= int(data[index])
index += 1
weight = []
value = []

for i in range(M):
    weight.append(int(data[index+i]))
index += M
for i in range(M):
    value.append(int(data[index+i]))    
#M * (N+1) ___  求得dp[M-1][N]
#dp[i][j]代表任选【0-i】的物品放入容量为j的背包里所得的最大价值
#dp[i][j] = max(dp[i-1][j], dp[i-1][j-weight[i]] + value[i])
dp = [[0] * (N + 1) for _ in range(M)]

#初始化(都是需要左上已知状态)
for j in range(N+1):
    if j >= weight[0]:
        dp[0][j] = value[0]

for i in range(1, M):
    for j in range(1, N+1):
        if j < weight[i]:
            dp[i][j] = dp[i-1][j]
        else:
            dp[i][j] = max(dp[i-1][j], dp[i-1][j-weight[i]] + value[i])
print(dp[M-1][N])

0-1背包问题——一维dp解决方案

其实就是复用每一行,细节在注释里

import sys
input = sys.stdin.read
data = input().split()
index = 0
M = int(data[index])
index += 1
N= int(data[index])
index += 1
weight = []
value = []

for i in range(M):
    weight.append(int(data[index+i]))
index += M
for i in range(M):
    value.append(int(data[index+i]))    
#由d[i][j] = max(d[i-1][j], d[i-1][j-weight[i]] + value[i])
#可知,d[i][j],只由上一行,即第i-1行决定
#且只由第i-1行的第j列及之前(左侧)的值决定
#则可以将i这个维度舍去,每次遍历时将第i-1行复用至第i行

dp = [0] * (N+1)
#dp[j] 代表容量为j的背包所能装的最大价值
#dp[j] = dp[j] 当 j < weihgt[i]
#dp[j] = max(dp[j], dp[j-weight[i] + value[i]]) 当 j >= weight[i]

#初始化
#由递推公式可知,只要dp初始化比计算的价值小,就可以成功覆盖
dp[0] = 0

#遍历顺序为行——列,因为我们是要复用每一行
for i in range(M):
    #倒序遍历的原因
    #由于每个j都需要用到左侧的值
    #如果先更新了j,那j之后的值就错乱了(左侧被覆盖)
    #且只需要遍历到weight[i],因为j比weight[i]小时,dp[j]不改动
    for j in range(N, weight[i]-1, -1):
        dp[j] = max(dp[j], dp[j-weight[i]]+ value[i])

print(dp[N])

lc416 分割等和子集问题

——0-1背包

class Solution:
    def canPartition(self, nums: List[int]) -> bool:
        sum_ = sum(nums)
        if sum_ % 2 == 1:
            return False
        #背包容量为target
        #物品价值 == 物品重量
        target = sum_ // 2

        #求容量为target的背包,能够装的最大价值
        #如果最大价值 < target,则背包无法装满
        #如果最大价值 == target,则背包可以装满
        #由于价值==重量,所以不会出现最大价值 > target的情况

        #dp[j]代表容量为j的背包装的最大价值
        dp = [0] * (target+1)
        dp[0] = 0
        for i in range(len(nums)):
            for j in range(target, nums[i]-1, -1):
                dp[j] = max(dp[j], dp[j- nums[i]] + nums[i])
        if dp[target] == target:
            return True
        return False

lc1049 最后一块石头的重量II

与lc416分割等和子集基本一样

尽可能分为两堆重量相近的石堆

则以sum // 2为背包容量,石头重量==石头价值,求背包最大价值

class Solution:
    def lastStoneWeightII(self, stones: List[int]) -> int:
        target = sum(stones) // 2
        dp = [0] * (target+1)
        for i in range(len(stones)):
            for j in range(target, stones[i]-1, -1):
                dp[j] = max(dp[j], dp[j-stones[i]] + stones[i])
        return sum(stones) - 2 * dp[target]

lc494 目标和

二维方法

class Solution:
    def findTargetSumWays(self, nums: List[int], target: int) -> int:
        if (sum(nums) + target) % 2 != 0:
            return 0
        if sum(nums) < abs(target):
            return 0
        positives = (sum(nums) + target) // 2
        n = len(nums)
        #dp[i][j] 代表用【0-i】的物品,填满容量为j的背包共有dp[i][j]种方法
        #当j >= nums[i]   dp[i][j] = dp[i-1][j] + dp[i-1][j-nums[i]]
        #当j < nums[i] dp[i][j] = d[i-1][j]

        #求dp[n-1][positives] 即用nums装满容量为positives的背包的方法数
        dp = [[0] * (positives+1) for _ in range(n)]

        #初始化
        if positives >= nums[0]:
            dp[0][nums[0]] = 1
        
        zero_nums = 0
        for i in range(n):
            if nums[i] == 0:
                zero_nums += 1
            dp[i][0] = pow(2, zero_nums)
        
        for i in range(1, n):
            for j in range(1, positives+1):
                if j < nums[i]:
                    dp[i][j] = dp[i-1][j]
                else:
                    dp[i][j] = dp[i-1][j] + dp[i-1][j-nums[i]]
        return dp[n-1][positives]

一维方法

class Solution:
    def findTargetSumWays(self, nums: List[int], target: int) -> int:
        if (sum(nums) + target) %2 != 0:
            return 0
        if sum(nums) < abs(target):
            return 0
        
        positives = (sum(nums) + target) // 2
        #去掉维度i之后,遍历到i的位置,代表可以使用【0-i】物品
        #只不过我们在不断更新、复用dp这一行
        #dp[j] 表示填满容量为j的背包有dp[j]种方法
        #dp[j] = dp[j] + dp[j-nums[i]]
        dp = [0] * (positives + 1)

        #初始化
        #这里还没开始遍历,所以认为物品0也是不可使用的
        #则填满背包容量为0的方法只能是1种(不放任何东西)
        dp[0] = 1
        
        for i in range(len(nums)):
            for j in range(positives, nums[i] - 1, -1):
                dp[j] = dp[j] + dp[j-nums[i]]
        return dp[positives]

lc474 一和零

这题其实是三维的01背包问题

容量可看作两个维度,分别是对0的容量和对1的容量
另一个维度就是物品了

三维方法

根据状态转移方程可知,第i层(也就是对于每个物品的遍历),是由第i-1层的状态决定的

1、dp含义(注释了)

2、初始化:

        初始化第0层即可

3、遍历顺序

        每层-每行-没列

class Solution:
    def findMaxForm(self, strs: List[str], m: int, n: int) -> int:
        def count(str):
            zeros = 0
            ones = 0
            for ch in str:
                if ch == '0':
                    zeros += 1
                else:
                    ones += 1
            return zeros, ones
        row = len(strs)
        #dp[i][j][k]:使用【0-i】物品,装最多有j个0和k个1的背包的最大子集长度
        #dp[i][j][k] = max(dp[i-1][j][k], dp[i-1][j-zeros][k-ones]+1)
        #求解dp[row-1][m][n]

        dp = [[[0] * (n+1) for _ in range(m+1)] for _ in range(row)]
        #初始化
        zeros, ones = count(strs[0])
        for j in range(m+1):
            for k in range(n+1):
                if j >= zeros and k >= ones:
                    dp[0][j][k] = 1
        
        #遍历
        for i in range(1, row):
            zeros, ones = count(strs[i])
            for j in range(m+1):
                for k in range(n+1):
                    if zeros <= j and ones <= k:
                        dp[i][j][k] = max(dp[i-1][j][k], dp[i-1][j-zeros][k-ones]+1)
                    else:
                        dp[i][j][k] = dp[i-1][j][k]
        return dp[row-1][m][n]

二维方法(复用每层)

没什么区别

注意遍历还是倒序(从后往前覆盖,覆盖过的值不再被本层参考,即不影响了)

class Solution:
    def findMaxForm(self, strs: List[str], m: int, n: int) -> int:
        def count(str):
            zeros = 0
            ones = 0
            for ch in str:
                if ch == '0':
                    zeros += 1
                else:
                    ones += 1
            return zeros, ones
        row = len(strs)

        # dp[j][k]:装容量为j个0,k个1的背包最大子集长度为dp[j][k]

        dp = [[0] * (n+1) for _ in range(m+1)]
        #此处不用初始化物品0,因为遍历时即可自动更新

        #遍历
        for i in range(row):
            zeros, ones = count(strs[i])
            for j in range(m, zeros-1, -1):
                for k in range(n, ones-1, -1):
                    dp[j][k] = max(dp[j][k], dp[j-zeros][k-ones]+1)
        return dp[m][n]

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值