算法训练第30天|46. 携带研究材料(01背包问题)|416. 分割等和子集

先讲一下01背包问题:

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

这道题目如果使用暴力解法,即回溯法来做的话,时间复杂度很大,,n表示物品的数量。

如果使用动态规划的方法来做:

1、确定dp数组以及下标的含义

我们需要使用二维数组的两个维度分别表示,物品 和 背包容量,即dp[i][j]。

i 来表示物品、j表示背包容量

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

2、确定递推公式

对于递推公式,首先我们要明确有哪些方向可以推导出 dp[i][j]

这里我们dp[1][4]的状态来举例:

dp[1][4] 来自于 放物品1 ,还是不放物品1。

如果不放物品1, 那么背包的价值应该是 dp[0][4] 即容量为4的背包,只放物品0的情况。

如果物品1, 那么背包要先留出物品1的容量,目前容量是4,物品1 需要重量为3,此时背包剩下容量为1。容量为1,只考虑放物品0 的最大价值是 dp[0][1],这个值我们之前就计算过。

所以 放物品1 的情况 = dp[0][1] + 物品1 的价值

两种情况,分别是放物品1 和 不放物品1,我们要取最大值

以上过程,抽象化如下:

  • 不放物品i :由dp[i - 1][j]推出,即背包容量为j,里面不放物品i的最大价值,此时dp[i][j]就是dp[i - 1][j]。

  • 放物品i :由dp[i - 1][j - weight[i]]推出,dp[i - 1][j - weight[i]] 为背包容量为j - weight[i]的时候不放物品i的最大价值,那么dp[i - 1][j - weight[i]] + value[i] (物品i的价值),就是背包放物品i得到的最大价值。

    递归公式dp[i][j] = max(dp[i - 1][j], dp[i - 1][j - weight[i]] + value[i]);

3、dp数组如何初始化

1.如果背包容量j为0的话,即dp[i][0],无论是选取哪些物品,背包价值总和一定为0。

2.dp[0][j],即:i为0,存放编号0的物品的时候,各个容量的背包所能存放的最大价值。

那么很明显当 j < weight[0]的时候,dp[0][j] 应该是 0,因为背包容量比编号0的物品重量还小。

j >= weight[0]时,dp[0][j] 应该是value[0],因为背包容量放足够放编号0物品。

3.**其他下标应该初始化多少?**dp[i][j] 是由左上方数值推导出来了,那么 其他下标初始为什么数值都可以,因为都会被覆盖。

4、确定遍历顺序

先遍历物品还是先遍历背包,其实都可以

5、举例推导dp数组


KamaCoder 46. 携带研究材料 (01背包问题)

题目链接:01背包问题

题目讲解:代码随想录

题目描述:小明是一位科学家,他需要参加一场重要的国际科学大会,以展示自己的最新研究成果。他需要带一些研究材料,但是他的行李箱空间有限。这些研究材料包括实验设备、文献资料和实验样本等等,它们各自占据不同的空间,并且具有不同的价值。

小明的行李空间为 N,问小明应该如何抉择,才能携带最大价值的研究材料,每种研究材料只能选择一次,并且只有选与不选两种选择,不能进行切割。

输入描述

第一行包含两个正整数,第一个整数 M 代表研究材料的种类,第二个正整数 N,代表小明的行李空间。

第二行包含 M 个正整数,代表每种研究材料的所占空间。

第三行包含 M 个正整数,代表每种研究材料的价值。

输出描述

输出一个整数,代表小明能够携带的研究材料的最大价值。

这道题目,M可以看成是物体中重量,N可以看成物体的价值。

Go 复制代码
package main

import(
    "fmt"
)

func max(a, b int) int{
    if a < b{
        a = b
    }
    return a
}

func main(){
    // 定义 物品种类,和背包大小
    var itemKinds, bagWeight int
    fmt.Scan(&itemKinds, &bagWeight)
    
    // 定义 每种物品所占背包的大小
    // 定义 每种物品的价值
    var weights, values []int
    weights = make([]int, itemKinds)
    values = make([]int, itemKinds)
    
    for i := 0; i < itemKinds; i++{
        fmt.Scan(&weights[i])
    }
    
    for i := 0; i < itemKinds; i++{
        fmt.Scan(&values[i])
    }
    
    // dp[i][j] -- 0--i物品任意选(选/不选),行李空间为j时 最大价值
    // dp[i][j] = dp[i-1][j] (不选物品i) , 选i dp[i-1][j-weight[i]] + values[i])
    dp := make([][]int, itemKinds)
    for i := 0; i < itemKinds; i++{
        dp[i] = make([]int, bagWeight+1)
    }
    
    
    // 初始化dp,当背包容量为0时,放不进任何物品,所以肯定为0
    // 对第一个物品,如果背包容量小于第一个物品的weight,则
    // 放不进;背包容量大于等于第一个物品的weight,则直接放第一个物品
    for j := weights[0]; j <= bagWeight; j++{
        dp[0][j] = values[0]
    }
    
    // 开始遍历,对每一物品,遍历背包容量
    for i := 1; i < itemKinds; i++{
        for j := 1; j <= bagWeight; j++{
            if j < weights[i]{ // 物品i的大小比当前背包容量大,物品i不放入背包
                dp[i][j] = dp[i-1][j]
            }else{ // 物品i放入背包中
                dp[i][j] = max(dp[i-1][j], dp[i-1][j-weights[i]] + values[i])
            }
        }
    }
    fmt.Println(dp[itemKinds-1][bagWeight])
}

LeetCode 416. 分割等和子集

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

题目讲解:代码随想录

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

  • 背包的大小为sum / 2
  • 物品 相当于集合里的元素,重量为元素的数值,价值也为元素的数值
Go 复制代码
func canPartition(nums []int) bool {
    sum := 0
    for _, num := range nums{
        sum += num
    }

    // 如果 nums 的总和为奇数,则不可能平分成两个子集
    if sum % 2 == 1{
        return false
    }

    target := sum / 2
    dp := make([][]int, len(nums))
    for i := 0; i < len(nums); i++{
        dp[i] = make([]int, target+1)
    }

    for j := nums[0]; j <= target; j++{
        dp[0][j] = nums[0]
    }

    for i := 1; i < len(nums); i++{
        for j := 0; j <= target; j++{
            if j < nums[i]{
                dp[i][j] = dp[i-1][j]
            }else{
                dp[i][j] = max(dp[i - 1][j], dp[i - 1][j - nums[i]] + nums[i])
            }
        }
    }

    return dp[len(nums)-1][target] == target
}

func max(a, b int)int{
    if a > b{
        return a
    }
    return b
}
相关推荐
qq_550337991 小时前
研1日记12
人工智能·算法·机器学习
buaichifanqie2 小时前
拓扑排序算法
c++·算法·排序算法·图论·拓扑排序
MogulNemenis2 小时前
力扣100题——动态规划
算法·leetcode·动态规划
sjsjs112 小时前
【数据结构-一维差分】力扣1854. 人口最多的年份
数据结构·算法·leetcode
weixin_486681142 小时前
C++系列-谓词predicate
开发语言·c++·算法
理论最高的吻3 小时前
104. 二叉树的最大深度【 力扣(LeetCode) 】
数据结构·c++·算法·leetcode·职场和发展·二叉树
风掣长空4 小时前
程序地址空间
linux·运维·算法
我明天再来学Web渗透4 小时前
【hot100-java】【对称二叉树】
java·开发语言·数据结构·算法·leetcode·链表·深度优先
戊子仲秋4 小时前
【LeetCode】每日一题 2024_9_14 从字符串中移除星号(模拟)
算法·leetcode·职场和发展
DeniuHe4 小时前
第4章 函数
开发语言·c++·算法