Day3-2021.1.11力扣322. 零钱兑换+377. 组合总和+139. 单词拆分+494. 目标和

2021年1月11日 时间都去哪了?

今日计划:整理背包问题的笔记。力扣题目刷到150+。

今日工作:力扣题目刷到150。
1.322. 零钱兑换[课后题]
2.377. 组合总和 Ⅳ[课后题]
3.139. 单词拆分[课后题]
4.494. 目标和[课后题]

Day3-2021.1.11力扣322. 零钱兑换+377. 组合总和+139. 单词拆分+494. 目标和_第1张图片

今日总结:
虎头蛇尾
新手表到了。真好。
专业的人做专业的事情。被拉去做小学辅导事倍功半、、、

今日语录:
人这一生最好的活法:“没事早点睡,有空多挣钱!
”能跑能跳,家人环绕,兜里有钱,脸上有笑。”

深圳女孩 的解释

转到2021.1.9-2021.1.31的learning record 首页

正文:

322. 零钱兑换[课后题]

呃呃呃。看官方题解吧:

题目要求:

给定不同面额的硬币 coins 和一个总金额 amount。编写一个函数来计算可以凑成总金额所需的最少的硬币个数。

如果没有任何一种硬币组合能组成总金额,返回 -1。你可以认为每种硬币的数量是无限的。

题解:

F(S)设为组成金额 S最少的硬币数,设最后一枚硬币的面值是 C。那么由于问题的最优子结构,转移方程应为:F(S) = F(S - C) + 1。加1是因为增加了一枚硬币。但我们不知道最后一枚硬币的面值是多少,所以我们需要枚举每个硬币面额值,并选择其中的最小值。

边界条件:当S=0时,也就是金额为0,那么F(S)=F(0)=0。遍历完每个硬币面额值,如果没有任何一种硬币组合能组成总金额,则返回 -1。

(1)自上而下+记忆化搜索

其实都需要背诵的。

// 递归+记忆化搜索
public class p9_ac7_322 {
    public static void main(String[] args) {
        int[] coins = new int[]{1, 2, 5};
        int amount = 11;
        System.out.println(coinChange(coins, amount));
    }

    public static int coinChange(int[] coins, int amount) {
        if(amount<=0){
            return 0;
        }
        int[] memo=new int[amount+1];
        return tryCoinChange(coins,amount,memo);
    }
    private static int tryCoinChange(int[] coins, int amount,int[] memo){
        if(amount<0){
            return -1;
        }
        if(amount==0){
            return 0;
        }
        if(memo[amount]!=0){
            return memo[amount];
        }
        int min=Integer.MAX_VALUE;
        for(int i=0;i<coins.length;i++){
            int res=tryCoinChange(coins,amount-coins[i],memo);
            if(res!=-1){
                min=Math.min(min,res+1);
            }
        }
        memo[amount]=(min==Integer.MAX_VALUE)?-1:min;
        return memo[amount];
    }
}
(2)动态规划

采用自下而上的方式进行思考。仍定义 F(i)为组成金额 i所需最少的硬币数量,假设在计算 F(i)之前,我们已经计算出 F(0)到F(i-1)的答案。 则 F(i)对应的转移方程应为 F(i)=1+min( F(0)到F(i-1) )

public class p9_ac7_322_1 {
    public static void main(String[] args) {
        int[] coins = new int[]{1, 2, 5};
        int amount = 11;
        System.out.println(coinChange(coins, amount));
    }

    private static int coinChange(int[] coins, int amount) {
        if (amount <= 0) {
            return 0;
        }
        int[] memo = new int[amount];
        return tryCoinChange(coins, amount, memo);
    }

    private static int tryCoinChange(int[] coins, int amount, int[] memo) {
        // 边界条件
        if (amount < 0) {
            return -1;
        }
        if (amount == 0) {
            return 0;
        }
        // 记忆化数组
        if (memo[amount - 1] != 0) {
            return memo[amount - 1];
        }
        // 业务代码
        // min 是最小货币数量
        int min = Integer.MAX_VALUE;
        // 遍历硬币数组
        for (int i = 0; i < coins.length; i++) {
            // res 是货币数量
            int res = tryCoinChange(coins, amount - coins[i], memo);
            if (res != -1) {
                min = Math.min(min, res + 1);
            }
        }
        // 这里需要判断,如果min是极大值,则没有找到需求的结果,则返回-1.
        // 如果找到了,那么就返回 获得的最小的硬币数量。
        memo[amount - 1] = (min == Integer.MAX_VALUE) ? -1 : min;
        return memo[amount - 1];
    }
}

377. 组合总和 Ⅳ

给定一个由正整数组成且不存在重复数字的数组,找出和为给定目标正整数的组合的个数。

输入数组的每个元素可以使用多次,这一点和「完全背包」问题有点像;

状态:dp[i] :对于给定的由正整数组成且不存在重复数字的数组,和为 i 的组合的个数。

输出:就是最后一个状态 dp[n]

状态转移方程:dp[i] = sum{dp[i - num] for num in nums and if i >= num}

定义 dp[0] = 1的,它表示如果 nums 里有一个数恰好等于 target,它单独成为 11 种可能。

(1)动态规划
public class p9_ac7_377 {
    public static void main(String[] args) {
        int[] nums = new int[]{1, 2, 3};
        int target = 4;
        System.out.println(combinationSum4(nums, target));
    }

    // 状态转移方程:dp[i]= dp[i - nums[0]] + dp[i - nums[1]] + dp[i - nums[2]] + ... (当 [] 里面的数 >= 0)
    // 特别注意:dp[0] = 1,表示,如果那个硬币的面值刚刚好等于需要凑出的价值,这个就成为 1 种组合方案

    // 输入非重复的正整数数组,输入目标和
    public static int combinationSum4(int[] nums, int target) {
        // dp数组从0到target+1
        int[] dp = new int[target + 1];
        // 这个值被其它状态参考,设置为 1 是合理的
        // 在 0 这一点,我们定义 dp[0] = 1 的,它表示如果 nums 里有一个数恰好等于 target,它单独成为 1 种可能。
        dp[0] = 1;

        for (int i = 1; i <= target; i++) {
            for (int j = 0; j < nums.length; j++) {
                // i是遍历的target。如果目标值≥此时的数据nums[j]
                if (nums[j] <= i) {
                    // 题目要求是输出组合的个数。i - nums[j]是说存入了nums[j]这个数据,剩余的数据有多少种组合。
                    dp[i] += dp[i - nums[j]];
                }
            }
        }
        return dp[target];
    }
}

139. 单词拆分

给定一个非空字符串 s 和一个包含非空单词的列表 wordDict,判定 s 是否可以被空格拆分为一个或多个在字典中出现的单词。字典中出现的单词可以重复使用

dp是布尔值型数组。dp[i],表示字符串 s 前 i个字符组成的字符串 s[0…i-1]是否能被空格拆分成若干个字典中出现的单词。

(1)动态规划
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

public class p9_ac7_139 {
    public static void main(String[] args) {
        String s = "applepenapple";
        List<String> wordDict = new ArrayList<>();
        wordDict.add("apple");
        wordDict.add("pen");
        System.out.println(wordBreak(s, wordDict));
    }

    public static boolean wordBreak(String s, List<String> wordDict) {
        // HashSet用来 检查一个字符串是否出现在给定的字符串列表里
        // wordDictSet的内容是 单词的列表 wordDict
        Set<String> wordDictSet = new HashSet(wordDict);
        // s.length()+1 dp的索引是从0到s.length()。
        boolean[] dp = new boolean[s.length() + 1];
        dp[0] = true;
        for (int i = 1; i <= s.length(); i++) {
            for (int j = 0; j < i; j++) {
                // s.substring(j, i) 这是一个String的截取字符串的方法,从第j个开始到i个。
                // 判定 s 是否可以被空格拆分为一个或多个在字典中出现的单词。
                if (dp[j] && wordDictSet.contains(s.substring(j, i))) {
                    dp[i] = true;
                    // 这里使用的是break哦。
                    break;
                }
            }
        }
        return dp[s.length()];
    }
}

494. 目标和

给定一个非负整数数组,a1, a2, …, an, 和一个目标数,S。现在你有两个符号 + 和 -。

对于数组中的任意一个整数,你都可以从 + 或 -中选择一个符号添加在前面。

返回可以使最终数组和为目标数 S 的所有添加符号的方法数。

题解:

(1)动态规划

dp数组需要记录的数据就是具体的方法数。

dp [i] [j]定义为从数组nums中 0 - i 的元素进行加减可以得到 j 的方法数量。

这道题的关键是nums[i]是加还是减。看下面是 j ± nums[ i ] 。可以理解为nums[i]这个元素我可以执行加,还可以执行减,那么我dp[i] [j]的结果值就是加/减之后对应位置的和。

dp[ i ] [ j ] = dp[ i - 1 ] [ j - nums[ i ] ] + dp[ i - 1 ] [ j + nums[ i ] ]

public class p9_ac7_494 {
    public static void main(String[] args) {
        int[] nums = new int[]{1, 1, 1, 1, 1};
        int s = 3;
        System.out.println(findTargetSumWays(nums, s));
    }

    public static int findTargetSumWays(int[] nums, int s) {
        int sum = 0;
        for (int i = 0; i < nums.length; i++) {
            sum += nums[i];
        }
        // 绝对值范围超过了sum的绝对值范围则无法得到
        if (Math.abs(s) > Math.abs(sum)) return 0;

        int len = nums.length;
        // - 0 +
        int t = sum * 2 + 1;
        int[][] dp = new int[len][t];
        // 初始化
        if (nums[0] == 0) {
            dp[0][sum] = 2;
        } else {
            dp[0][sum + nums[0]] = 1;
            dp[0][sum - nums[0]] = 1;
        }

        for (int i = 1; i < len; i++) {
            for (int j = 0; j < t; j++) {
                // 边界
                int l = (j - nums[i]) >= 0 ? j - nums[i] : 0;
                int r = (j + nums[i]) < t ? j + nums[i] : 0;
                dp[i][j] = dp[i - 1][l] + dp[i - 1][r];
            }
        }
        return dp[len - 1][sum + s];
    }
}

你可能感兴趣的:(Day3-2021.1.11力扣322. 零钱兑换+377. 组合总和+139. 单词拆分+494. 目标和)