夸父追日:第九章 动态规划part06

今日收获:零钱兑换,完全平方数,单词拆分,多重背包

1. 零钱兑换

题目链接:322. 零钱兑换 - 力扣(LeetCode)

思路:完全背包的组合/排列问题,因为组成金额的硬币最小个数都一样。

(1)dp数组表示组成下标为 j 的金额所需的最小硬币数。

(2)初始化:除了dp为0的位置,其余位置都要初始化为整数最大值,否则会递推公式的计算值一直是0。

(3)遍历顺序:因为是组合或排列都可以,所以先遍历物品再背包或者先背包再物品都可以;因为物品可以无限选择,所以背包容量是从小到大。

(3)递推公式:当不选择当前硬币时,只有dp[j-coins[i]]位置不为最大值才成立,说明存在硬币可以组成金额 j 。

(4)判断结果:最后还要将dp[amount]的值和最大值比较,如果不是最大值才说明有硬币可以组成当前的金额。

方法:

class Solution {
    public int coinChange(int[] coins, int amount) {
        // 完全背包的组合问题
        int len=coins.length;

        int[] dp=new int[amount+1];  // 组成总金额为i的最少硬币数

        // 初始化
        dp[0]=0;
        for (int i=1;i<amount+1;i++){
            dp[i]=Integer.MAX_VALUE;  // 其他位置初始化为最大值,否则递归公式计算的值会一直是0
        }

        for (int i=0;i<len;i++){
            for (int j=0;j<amount+1;j++){
                // 只有dp[j-coins[i]]不是最大值时,才有选择的可能
                if (j>=coins[i]&&dp[j-coins[i]]!=Integer.MAX_VALUE){
                    dp[j]=Math.min(dp[j],dp[j-coins[i]]+1);
                }
            }
        }

        return dp[amount]==Integer.MAX_VALUE?-1:dp[amount];
    }
}

2. 完全平方数

题目链接:279. 完全平方数 - 力扣(LeetCode)

思路:类似于上一题零钱兑换,只不过把零钱换成了从1开始的平方数

方法:

class Solution {
    public int numSquares(int n) {
        int[] dp=new int[n+1];
        int max=Integer.MAX_VALUE;  
        int sqrt=(int)Math.sqrt(n);

        // 初始化
        dp[0]=0;
        for (int i=1;i<n+1;i++){
            dp[i]=max;
        }

        for (int i=1;i<=sqrt;i++){
            for (int j=0;j<n+1;j++){
                if (j>=i*i&&dp[j-i*i]!=max){
                    dp[j]=Math.min(dp[j],dp[j-i*i]+1);
                }
            }
        }

        return dp[n];
    }
}

3. 单词拆分

题目链接:139. 单词拆分 - 力扣(LeetCode)

思路:完全背包的排列问题。

(1)dp数组的定义:表示到字符串位置为 i 的子串是否能被字典表示。

(2)初始化子串长度为0时为true,其余子串位置均为false。

(3)遍历顺序:先遍历背包再遍历物品,判断物品是否在字典中。

(4)递推公式:如果当前子串在字典中并且上一个位置的子串为true,则当前位置表示的子串也能被字典表示。

方法:

class Solution {
    public boolean wordBreak(String s, List<String> wordDict) {
        // 完全背包的排列问题
        // 判断字典中的单词是否能装满目标字符串

        boolean[] dp=new boolean[s.length()+1];  // 判断长度为i的字符串是否能由字典组成

        // 初始化
        for (boolean d:dp){
            d=false;
        }
        dp[0]=true;

        // 先遍历背包再遍历物品求排列
        // 物品要通过判断子字符串是否存在字典中判断
        HashSet<String> set=new HashSet<>(wordDict);
        for (int i=1;i<=s.length();i++){
            for (int j=0;j<i;j++){
                if (set.contains(s.substring(j,i))&&dp[j]){
                    dp[i]=true;
                }
            }
        }

        return dp[s.length()];
    }
}

4. 多重背包(了解就好)

概念:每种物品的数量有限制

题目链接:56. 携带矿石资源(第八期模拟笔试) (kamacoder.com)

思路:在遍历物品再倒序遍历背包的循环中,再添加一层循环遍历物品的数量。物品的数量num从1开始,当前背包的容量要大于num*物品重量,价值也要加上num倍

方法:

import java.util.Scanner;

public class Main{
    public static void main(String[] args){
        Scanner sc=new Scanner(System.in);
        
        int C=sc.nextInt();
        int N=sc.nextInt();
        
        int[] w=new int[N];
        int[] v=new int[N];
        int[] k=new int[N];
        
        for (int i=0;i<N;i++){
            w[i]=sc.nextInt();
        }
        
        for (int i=0;i<N;i++){
            v[i]=sc.nextInt();
        }
        
        for (int i=0;i<N;i++){
            k[i]=sc.nextInt();
        }
        
        int[] dp=new int[C+1];
        
        // 先遍历物品再倒序遍历背包
        for (int i=0;i<N;i++){
            for (int j=C;j>=w[i];j--){
                // 遍历物品的数量
                for (int num=1;num<=k[i]&&j>=num*w[i];num++){
                    dp[j]=Math.max(dp[j],dp[j-num*w[i]]+num*v[i]);
                }
            }
        }
        
        System.out.println(dp[C]);
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值