代码随想录算法训练营第42天 | 01背包问题理论基础 + 416.分割等和子集

今日任务

目录

01背包问题 二维数组

01背包问题 一维/滚动数组

416.分割等和子集 - Medium


01背包问题 二维数组

理论基础:代码随想录

01 背包

有n件物品和一个最多能背重量为w 的背包。第i件物品的重量是weight[i],得到的价值是value[i] 。每件物品只能用一次,求解将哪些物品装入背包里物品价值总和最大。

暴力解法:

  • 每一件物品其实只有两个状态,取或者不取,所以可以使用回溯法搜索出所有的情况
  • 暴力的解法是指数级别的时间复杂度。进而才需要动态规划的解法来进行优化

二维dp数组

  • 1 确定dp数组及含义:

        dp[i][j] 表示从下标为[0-i]的物品里任意取,放进容量为j的背包,价值总和最大是多少

代码随想录算法训练营第42天 | 01背包问题理论基础 + 416.分割等和子集_第1张图片

  • 2 递推公式:dp[i][j] = max(dp[i - 1][j], dp[i - 1][j - weight[i]] + value[i])
  • 3 dp数组初始化:所有dp[i][0]均为0;当 j < weight[0]的时候 dp[0][j] 是 0,当j >= weight[0]时,dp[0][j] 是value[0]
  • 4 确定遍历顺序:有两个遍历的维度,物品与背包重量,先遍历物品更好理解
  • 5 推导dp数组:如下,无参数版
def test_2_wei_bag_problem1():
    weight = [1, 3, 4]
    value = [15, 20, 30]
    bagweight = 4

    # 二维数组
    dp = [[0] * (bagweight + 1) for _ in range(len(weight))]

    # 初始化
    for j in range(weight[0], bagweight + 1):
        dp[0][j] = value[0]

    # weight数组的大小就是物品个数
    for i in range(1, len(weight)):  # 遍历物品
        for j in range(bagweight + 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[len(weight) - 1][bagweight])

test_2_wei_bag_problem1()

01背包问题 一维/滚动数组

理论基础:代码随想录

推荐 使用一维dp数组的写法,比较直观简洁,而且空间复杂度还降了一个数量级 

在二维dp数组的基础上,其实可以把dp[i - 1]那一层拷贝到dp[i]上,表达式完全可以是:dp[i][j] = max(dp[i][j], dp[i][j - weight[i]] + value[i]);与其把dp[i - 1]这一层拷贝到dp[i]上,不如只用一个一维数组了,只用dp[j](一维数组,也可以理解是一个滚动数组)。

这就是滚动数组的由来,需要满足的条件是上一层可以重复利用,直接拷贝到当前层。

滚动dp数组:

  • 1 dp[j]:容量为j的背包,所背的物品价值可以最大为dp[j]
  • 2 递推公式:dp[j]有两个选择,一个是取自己dp[j] 相当于 二维dp数组中的dp[i-1][j],即不放物品i;一个是取dp[j - weight[i]] + value[i],即放物品i,指定是取最大的
    dp[j] = max(dp[j], dp[j - weight[i]] + value[i])
  • 3 初始化:都初始为0就可以了
  • 4 遍历顺序:一维dp遍历的时候,背包是从大到小,倒序遍历是为了保证物品i只被放入一次,每次取得状态不会和之前取得状态重合,这样每种物品就只取一次了;两个嵌套for循环的顺序,先遍历物品嵌套遍历背包容量,不可颠倒
  • 5 推导dp数组:无参数版
    def test_1_wei_bag_problem():
        weight = [1, 3, 4]
        value = [15, 20, 30]
        bagWeight = 4
    
        # 初始化
        dp = [0] * (bagWeight + 1)
        for i in range(len(weight)):  # 遍历物品
            for j in range(bagWeight, weight[i] - 1, -1):  # 遍历背包容量
                dp[j] = max(dp[j], dp[j - weight[i]] + value[i])
    
        print(dp[bagWeight])
    
    
    test_1_wei_bag_problem()

416.分割等和子集 - Medium

题目链接:力扣-416. 分割等和子集

给你一个 只包含正整数 的 非空 数组 nums 。请你判断是否可以将这个数组分割成两个子集,使得两个子集的元素和相等。 

提示: 转换成背包容易为 1/2 数组和的 01背包问题

class Solution:
    def canPartition(self, nums: List[int]) -> bool:
        if sum(nums) % 2 == 1: return False

        bagWeight = sum(nums) // 2
        dp = [0] * (bagWeight + 1)
        for i in range(len(nums)):  # 遍历物品
            for j in range(bagWeight, nums[i] - 1, -1):  # 遍历背包容量
                dp[j] = max(dp[j], dp[j - nums[i]] + nums[i])
        
        if dp[bagWeight] == bagWeight:
            return True
        else:
            return False

你可能感兴趣的:(LeetCode,leetcode,python,动态规划)