代码随想录算法训练营day44 || 52. 完全背包问题,518. 零钱兑换II,377. 组合总和 Ⅳ

视频讲解:

带你学透完全背包问题! 和 01背包有什么差别?遍历顺序上有什么讲究?_哔哩哔哩_bilibili

动态规划之完全背包,装满背包有多少种方法?组合与排列有讲究!| LeetCode:518.零钱兑换II_哔哩哔哩_bilibili

动态规划之完全背包,装满背包有几种方法?求排列数?| LeetCode:377.组合总和IV_哔哩哔哩_bilibili

52. 完全背包问题

思路:与所预料的一样,就是01背包问题的重量遍历顺序从头开始遍历即可求解,另外优化的地方在于重量可以从当前物品对应的重量开始进行遍历,因此按物品外重量内的遍历顺序来遍历的话,只有j>=weights[i]才会进行递推,所以不妨直接在for循环赋值上就体现出来。

代码随想录 ==>

01背包中二维dp数组的两个for遍历的先后循序是可以颠倒了,一维dp数组的两个for循环先后循序一定是先遍历物品,再遍历背包容量。

在完全背包中,对于一维dp数组来说,其实两个for循环嵌套顺序是无所谓的!

因为dp[j] 是根据下标j之前所对应的dp[j]计算出来的。 只要保证下标j之前的dp[j]都是经过计算的就可以了。

import java.util.*;

// 时间复杂度O(n^2)
// 空间复杂度O(n)

public class Main {

    public static void main(String[] args) {
        Scanner scanner = new Scanner(System.in);
        int N = scanner.nextInt();
        int V = scanner.nextInt();
        int[] weights = new int[N];
        int[] values = new int[N];

        for(int i=0; i<N; i++){
            weights[i] = scanner.nextInt();
            values[i] = scanner.nextInt();
        }

        // dp数组的值表示的是放入的最大的价值
        int[][] dp = new int[N][V+1];
        for(int j=1; j<=V; j++){
            if(j >= weights[0])
                dp[0][j] = dp[0][j-weights[0]]+values[0];
        }
        for(int i=1; i<N; i++){
            for(int j=1; j<=V; j++){
                if(j >= weights[i]){
                    dp[i][j] = Math.max(dp[i-1][j], dp[i][j-weights[i]]+values[i]);
                }
                else
                    dp[i][j] = dp[i-1][j];
            }
        }

        System.out.println(dp[N-1][V]);
        return;
    }
}

 

518. 零钱兑换II

思路:(题目类型:放满完全背包问题),但是却是第一次遇到的存在不可放的情况的题目。由于统计的是可行的找零策略,是求组合的问题。而针对不可行数值,无需进行额外的操作,仍然保留dp[j]+=dp[j-nums[i]]的递推公式,不可找零的数值将存在传递性,可自行将当前位置的不可行传递到下一个位置。这个策略的存在为之后的 零钱兑换 提供关键的思路。

// 时间复杂度O(n^2)
// 空间复杂度O(n)

class Solution {
    public int change(int amount, int[] coins) {
        
        // coins的所有零钱种类作为物品数量
        int N = coins.length;
        // dp数组表示零钱i可有几种可行的找零策略
        int[] dp = new int[amount+1];
        // 固定搭配
        dp[0] = 1;
        
        // 可重复放值且组合形式的遍历
        for(int i=0; i<N; i++){
            for(int j=coins[i]; j<=amount; j++){
                dp[j] += dp[j-coins[i]];
            }
        }

        return dp[amount];
    }
}

377. 组合总和 Ⅳ

思路:关键理解物品与重量循环顺序颠倒后,即将求组合变成了求排列。(题目类型:放满完全背包问题)

// 时间复杂度O(n^2)
// 空间复杂度O(n)

class Solution {
    public int combinationSum4(int[] nums, int target) {
        // dp数组的含义是容量为j的背包可以由由nums数组的元素组成的方式有几种
        int[] dp = new int[target+1];
        // 递推公式, dp[j] += dp[j-nums[i]]
        // 初始化,仅仅是为了推导
        dp[0] = 1;
        // !! 求组合,外层for循环遍历物品;求排列,则外层for循环遍历重量,从而可以使得访问物品
        // 外层for循环是物品的时候,每次更新的是整个dp,意义在于判断各个重量可以加入几个当前的nums[i],更新的牵扯也是关联到j-nums[i]的位置,
        // 外层for循环是重量的时候,每次更新的是dp数组中的某个位置,是用所有的物品去去试探当前重量可以放入几种物品,5-2和5-3两个位置的内容会加到一个位置里面去,而上一种里面,5-2加上了dp[3],但此时所考虑的物品仅仅是i之前的,i之后的不考虑;所以3,5都是由1和2拼出来的;只有到了3之后,才会将1,2,3三个都考虑;
        // 但是外层for循环重量的时候,是在每个重量,都去考虑1,2,3,所以5是从4,1  3,2  2,3 1,4 这样的全面进行考虑的
        for(int j=1; j<=target; j++){
            for(int i=0; i<nums.length; i++)
                if(j >= nums[i])
                    dp[j] += dp[j-nums[i]];
        }

        return dp[target];
    }
}

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值