算法练习day38

112 阅读3分钟

一、完全背包

有n件物品,每件物品有无限多个,一个最多能背W的背包,第i件物品的重量为weight[i],价值为value[i],求将哪些物品放入背包,得到的价值总和最大

01背包和完全背包的唯一不同就在于遍历顺序

01背包的核心代码

for(let i = 0; i <= weight.length;i++) { // 遍历物品
    for(let j = bagWeight; j >= weight[i];j--) { // 遍历背包容量
        dp[j] = Math.max(dp[j], dp[j - weight[i]] + weight[i])
    }
}

完全背包的物品可以多次添加,所以需要从小到大正序遍历

for(let i = 0; i <= weight.length;i++) { // 遍历物品
    for(let j = weight[i]; j <= bagWeight ;j++) { // 遍历背包容量
        dp[j] = Math.max(dp[j], dp[j - weight[i]] + weight[i])
    }
}

对于01背包,二维dp数组,两个for循环嵌套顺序是无所谓的,一维dp数组的两个for循环必须是先遍历物品,后遍历背包

对于完全背包,一维dp,两个for循环嵌套顺序是无所谓的,只要保证下标j之前的dp[j]都是经过计算的就可以

const readline = require('readline');
const rl = readline.createInterface({
    input: process.stdin,
    output: process.stdout
});
const inputArr = [];//存放输入的数据
rl.on('line', function(line){
  //line是输入的每一行,为字符串格式
    inputArr.push(line.split(' '));//将输入流保存到inputArr中(注意为字符串数组)
}).on('close', function(){
    console.log(fun(inputArr))//调用函数并输出
})

function fun(inputArr) {
    let [M, N] = inputArr[0].map(item => Number(item))
    let weight = []
    let value = []
    for(let [w, v] of inputArr.slice(1)) {
        weight.push(Number(w))
        value.push(Number(v))
    }
    
    let dp = new Array(N+1).fill(0)
    for(let i = 0; i < M; i++) {
        for(let j = weight[i]; j <= N;j++) {
            dp[j] = Math.max(dp[j], dp[j - weight[i]] + value[i])
        }
    }
    return dp[N]
    
}

二维dp

function fun(inputArr) {
    let [M, N] = inputArr[0].map(item => Number(item))
    let weight = []
    let value = []
    for(let [w, v] of inputArr.slice(1)) {
        weight.push(Number(w))
        value.push(Number(v))
    }
    let dp = new Array(M).fill(0).map(_ => new Array(N + 1).fill(0))
    for (let j = weight[0]; j <= N; j++) {
        dp[0][j] = value[0]
    }
    for (let i = 1; i < M; i++) {
        for (let j = 0; j <= N; j++) {
            if (j < weight[i-1]) {
                dp[i][j] = dp[i - 1][j]
            } else {
                dp[i][j] = Math.max(dp[i - 1][j], dp[i][j - weight[i-1]] + value[i-1])
            }
        }
    }
    return dp[M - 1][N]
}

二、零钱兑换2

求凑成总金额的物品组合个数

五部曲

  1. dp[j],凑成总金额j的组合数
  2. 确定递推公式,dp[j] += dp[j - coins[i]]
  3. dp初始化,dp[0] = 1,递归的基础,后序累加依赖于此,其他初始化为0,不影响累计
  4. 遍历顺序,必须是先外层循环钱币,内层循环金钱总额,否则,不同金额算出来是排列数
  5. 举例
/**
 * @param {number} amount
 * @param {number[]} coins
 * @return {number}
 */
var change = function(amount, coins) {
    let dp = new Array(amount + 1).fill(0)
    dp[0] = 1
    for(let i = 0; i < coins.length; i++) {
        for(let j = coins[i]; j <= amount;j++) {
            dp[j] += dp[j - coins[i]]
        }
    }
    return dp[amount]
};

三、组合总和4

本题其实是求排列

和上题的区别就在于遍历顺序,本题先外层遍历背包,内层遍历物品

/**
 * @param {number[]} nums
 * @param {number} target
 * @return {number}
 */
var combinationSum4 = function(nums, target) {
    let dp = new Array(target + 1).fill(0)
    dp[0] = 1
    for(let i = 1; i <= target; i++) {
        for(let j = 0; j < nums.length;j++) {
            if(i >= nums[j]) {
                dp[i] += dp[i - nums[j]]
            }
        }
    }
    return dp[target]
};