代码随想录算法训练营第45天

今日任务

● 70. 爬楼梯 (进阶) ● 322. 零钱兑换 ● 279.完全平方数 ● 139.单词拆分

链接:https://docs.qq.com/doc/DUFVEbWRyZlpjaGty (opens in a new tab)

70. 爬楼梯(进阶版)

题解想法

其实是一个完全背包问题。

1阶,2阶,.... m阶就是物品,楼顶就是背包。

每一阶可以重复使用,例如跳了1阶,还可以继续跳1阶。

问跳到楼顶有几种方法其实就是问装满背包有几种方法。

这是背包里求排列问题,即:1、2 步 和 2、1 步都是上三个台阶,但是这两种方法不一样!

所以需将背包放在外循环,将物品放在内循环。

import java.util.Scanner;
class climbStairs{
    public static void main(String [] args){
        Scanner sc = new Scanner(System.in);
        int m, n;
        while (sc.hasNextInt()) {
            // 从键盘输入参数,中间用空格隔开
            n = sc.nextInt();
            m = sc.nextInt();
 
            // 求排列问题,先遍历背包再遍历物品
            int[] dp = new int[n + 1];
            dp[0] = 1;
            for (int j = 1; j <= n; j++) {
                for (int i = 1; i <= m; i++) {
                    if (j - i >= 0) dp[j] += dp[j - i];
                }
            }
            System.out.println(dp[n]);
        }
    }
}

322. 零钱兑换

自己想法

class Solution {
    public int coinChange(int[] coins, int amount) {
        if (amount == 0) return 0;
        int[] dp = new int[amount + 1];
        Arrays.fill(dp, -1);
        for (int i = 0; i < coins.length; i++) {
            for (int j = coins[i]; j <= amount; j++) {
                if (dp[j] > 0 && dp[j - coins[i]] > 0) dp[j] = Math.min(dp[j], dp[j - coins[i]] + 1);
                else if (dp[j] <= 0 && dp[j - coins[i]] > 0) dp[j] = dp[j - coins[i]] + 1;
                else {
                    if (j - coins[i] == 0) {
                        if (dp[j] < 0) dp[j] = 1;
                        else dp[j] = Math.min(dp[j], 1);
                    }
                }
            }
        }
        return dp[amount];
    }
}

优化了一下初始化方式和递推判断方式

class Solution {
    public int coinChange(int[] coins, int amount) {
        if (amount == 0) return 0;
        int[] dp = new int[amount + 1];
        Arrays.fill(dp, Integer.MAX_VALUE);
        dp[0] = 0;
        for (int i = 0; i < coins.length; i++) {
            for (int j = coins[i]; j <= amount; j++) {
                if (dp[j - coins[i]] != Integer.MAX_VALUE) {
                    dp[j] = Math.min(dp[j], dp[j - coins[i]] + 1);
                }
            }
        }
        if (dp[amount] == Integer.MAX_VALUE) return -1;
        else return dp[amount];
    }
}

279.完全平方数

自己想法

完全背包,n是背包,平方数是物品。只是需要自己造物品

class Solution {
    private List<Integer> squares = new ArrayList<>();
    public int numSquares(int n) {
        squareListBuilder(n);
        int[] dp = new int[n + 1];
        Arrays.fill(dp, Integer.MAX_VALUE);
        dp[0] = 0;
        for (int i = 0; i < squares.size(); i++) {
            for (int j = squares.get(i); j <= n; j++) {
                if (dp[j - squares.get(i)] != Integer.MAX_VALUE) {
                    dp[j] = Math.min(dp[j], dp[j - squares.get(i)] + 1);
                }
            }
        }
        return dp[n];
    }
 
    private void squareListBuilder(int n) {
        int i = 1;
        while (i * i <= n) {
            squares.add(i * i);
            i++;
        }
    }
}

单独建一次平方数列表太费时间,优化:

class Solution {
    private List<Integer> squares = new ArrayList<>();
    public int numSquares(int n) {
        // squareListBuilder(n);
        int[] dp = new int[n + 1];
        Arrays.fill(dp, Integer.MAX_VALUE);
        dp[0] = 0;
        for (int i = 1; i * i <= n; i++) {
            for (int j = i * i; j <= n; j++) {
                if (dp[j - i * i] != Integer.MAX_VALUE) {
                    dp[j] = Math.min(dp[j], dp[j - i * i] + 1);
                }
            }
        }
        return dp[n];
    }
 
    // private void squareListBuilder(int n) {
    //     int i = 1;
    //     while (i * i <= n) {
    //         squares.add(i * i);
    //         i++;
    //     }
    // }
}

139.单词拆分

自己想法

我将题目变为“单词拼接”,下面以“拼”为讲述逻辑,而不是“拆”。

完全背包问题,字符串是背包,单词字典是物品。

同时可以理解为是排列问题,例如字符串"aba",单词字典b, 那么“aba”只能由“a”、“b”、“a”拼出,而不能由“a”、“a”、“b”拼出。 所以是排列。

因而背包放在外循环,物品放在内循环。

dp[i]表示字符串s的前i个字符能否由字典中的单词拼出。

class Solution {
    public boolean wordBreak(String s, List<String> wordDict) {
        boolean[] dp = new boolean[s.length() + 1];
        dp[0] = true;
        for (int j =0; j <= s.length(); j++) {
            for (int i = 0; i < wordDict.size(); i++) {
                // 递推判断条件:都满足时,才能递推dp[j] = true;
                // 1. 确保不越界:j - wordDict.get(i).length() >= 0
                // 2. 前面的字符串是可以拼出来的:dp[j - wordDict.get(i).length()]
                // 3. 当前查看的单词是否和字符串s从start_index开始的一段匹配:checkWord(s, wordDict.get(i), j - wordDict.get(i).length())
                if (j - wordDict.get(i).length() >= 0 &&
                dp[j - wordDict.get(i).length()] && 
                checkWord(s, wordDict.get(i), j - wordDict.get(i).length())) dp[j] = true;
            }
        }
        return dp[s.length()];
    }
    // 判断当前查看的单词是否和字符串s从start_index开始的一段匹配
    private boolean checkWord(String s, String word, int start_index) {
        for (int i = 0; i < word.length(); i++) {
            if (i + start_index >= s.length() || i + start_index < 0) return false;
            if (s.charAt(i + start_index) != word.charAt(i)) return false;
        }
        return true;
    }
}