Leecode Hot100

动态规划

Solution70. 爬楼梯

这里是引用

为什么 dp[i] = dp[i-1] + dp[i-2]?

由于每次你可以爬1或2个台阶,要爬到第i阶楼梯,你的最后一步只有两种可能:
从第 i-1 阶爬 1 个台阶上来。
从第 i-2 阶爬 2 个台阶上来。

换句话说:
如果你最后一步爬了 1 个台阶,那么你之前一定是在第 i-1 阶。
如果你最后一步爬了 2 个台阶,那么你之前一定是在第 i-2 阶。

因此,爬到第i阶的总方法数dp[i] = 从第i-1阶爬1个台阶上来的方法数dp[i-1] + 从第i-2阶爬2个台阶上来的方法数dp[i-2]
因此,dp[i] = dp[i-1] + dp[i-2]

也就是说不论n此刻是几:你最后一步只能爬 1 个台阶或 2 个台阶。

例子 1:n = 2
爬到第 2 阶有两种方法:①1 阶 + 1 阶 ②直接爬 2 阶。

例子 2:n = 3
爬到第 3 阶有三种方法:
    1 阶 + 1 阶 + 1 阶。
    1 阶 + 2 阶。
    2 阶 + 1 阶。
根据公式:
    dp[3] = dp[2] + dp[1]
    dp[2] = 2(爬到第 2 阶有两种方法)。
    dp[1] = 1(爬到第 1 阶有一种方法)。
    所以 dp[3] = 2 + 1 = 3。

例子 3:n = 4
爬到第 4 阶有五种方法:
    1 阶 + 1 阶 + 1 阶 + 1 阶。
    1 阶 + 1 阶 + 2 阶。
    1 阶 + 2 阶 + 1 阶。
    2 阶 + 1 阶 + 1 阶。
    2 阶 + 2 阶。
根据公式:
    dp[4] = dp[3] + dp[2]。
    dp[3] = 3(爬到第 3 阶有三种方法)。
    dp[2] = 2(爬到第 2 阶有两种方法)。
    所以 dp[4] = 3 + 2 = 5。
public class Solution {
   
   

    // 主方法,用于测试
    public static void main(String[] args) {
   
   
        int n = 5;
        System.out.println(climbStairs(n)); // 输出: 8
    }

    // 返回爬 n 阶楼梯的不同方法数
    public static int climbStairs(int n) {
   
   
        // 初始条件
        if (n == 0 || n == 1) {
   
   
            return 1;
        }

        // 使用两个变量存储 dp[i-1] 和 dp[i-2]
        int prev1 = 1; // dp[i-1]
        int prev2 = 1; // dp[i-2]
        int result = 0; // dp[i]

        // 动态规划计算 dp[i]
        for (int i = 2; i <= n; i++) {
   
   
            result = prev1 + prev2; // dp[i] = dp[i-1] + dp[i-2]
            prev2 = prev1; // 更新 dp[i-2]
            prev1 = result; // 更新 dp[i-1]
        }

        // 返回 dp[n]
        return result;
    }
}

Solution118. 杨辉三角

这里是引用

每一行的第一个数字是 1
每一行的最后一个数字是 1
每一行中间的数字row(j)=prevRow.get(j - 1) + prevRow.get(j)

         [1]
        [1, 1]
       [1, 2, 1]
      [1, 3, 3, 1]
     [1, 4, 6, 4, 1]
public class Solution {
   
   

    // 主方法,用于测试
    public static void main(String[] args) {
   
   
        int numRows = 5;
        List<List<Integer>> triangle = generate(numRows);
        for (List<Integer> row : triangle) {
   
   
            System.out.println(row);
        }
        // 输出:
        // [1]
       // [1, 1]
      // [1, 2, 1]
     // [1, 3, 3, 1]
    // [1, 4, 6, 4, 1]
    }

    // 生成杨辉三角的前 numRows 行
    public static List<List<Integer>> generate(int numRows) {
   
   
        // 初始化杨辉三角
        List<List<Integer>> triangle = new ArrayList<>();

        // 遍历每一行
        for (int rowNum = 0; rowNum < numRows; rowNum++) {
   
   
            // 初始化当前行
            List<Integer> row = new ArrayList<>();

            // 每一行的第一个数字是 1
            row.add(1);

            // 填充中间的数字
            if (rowNum > 0) {
   
   
                // 获取上一行
                List<Integer> prevRow = triangle.get(rowNum - 1);
                // 遍历上一行,计算当前行的中间数字
                for (int j = 1; j < rowNum; j++) {
   
   
                    row.add(prevRow.get(j - 1) + prevRow.get(j));
                }
                // 每一行的最后一个数字是 1
                row.add(1);
            }

            // 将当前行加入杨辉三角
            triangle.add(row);
        }

        // 返回生成的杨辉三角
        return triangle;
    }
}

Solution198. 打家劫舍

这里是引用

    定义 dp[i] 表示偷窃到第 i 间房屋时能够获得的最大金额。

    状态转移方程:
    - 如果偷窃第 i 间房屋,那么不能偷窃第 i-1 间房屋,最大金额为 dp[i-2] + nums[i]。
    - 如果不偷窃第 i 间房屋,那么最大金额为 dp[i-1]。
    所以:dp[i] = max(dp[i-1], dp[i-2] + nums[i]),如dp[3] = max(dp[2], dp[1] + nums[3])

    初始条件:
    dp[0] = nums[0](只有一间房屋时,只能偷窃这一间)。
    dp[1] = max(nums[0], nums[1])(有两间房屋时,选择金额较大的一间偷窃)。

public class Solution {
   
   

    // 主方法,用于测试
    public static void main(String[] args) {
   
   
        int[] nums = {
   
   2, 7, 9, 3, 1};
        System.out.println(rob(nums)); // 输出: 12
    }

    // 返回能够偷窃到的最高金额
    public static int rob(int[] nums) {
   
   
        // 如果数组为空,返回 0
        if (nums == null || nums.length == 0) {
   
   
            return 0;
        }
        // 如果只有一间房屋,返回该房屋的金额
        if (nums.length == 1) {
   
   
            return nums[0];
        }

        // 用两个变量存储 dp[i-1] 和 dp[i-2]
        int prev2 = nums[0]; // dp[i-2]
        int prev1 = Math.max(nums[0], nums[1]); // dp[i-1]
        int result = prev1; // dp[i]

        // 动态规划计算 dp[i]
        for (int i = 2; i < nums.length; i++) {
   
   
            result = Math.max(prev1, prev2 + nums[i]); // dp[i] = max(dp[i-1], dp[i-2] + nums[i])
            prev2 = prev1; // 更新 dp[i-2]
            prev1 = result; // 更新 dp[i-1]
        }

        // 返回 dp[n-1]
        return result;
    }
}

Solution279. 完全平方数

这里是引用

我们需要找到和为 n 的完全平方数的最少数量。例如:
- 如果 n = 12,我们可以用 4 + 4 + 4(即 2^2 + 2^2 + 2^2),所以最少需要 3 个完全平方数。
- 如果 n = 13,我们可以用 9 + 4(即 3^2 + 2^2),所以最少需要 2 个完全平方数。

dp[i] 表示和为 i 的完全平方数的最少数量。
对于每个 i,我们尝试用 所有可能的完全平方数 来组成 i。
假设我们选择了一个完全平方数 j*j,那么剩下的部分就是 i - j*j。
我们需要找到 i - j*j 的最少完全平方数数量,然后加上 1(因为我们已经用了一个 j*j)。

状态转移方程
对于每个 i,我们遍历所有可能的 j(满足 j*j <= i)。
对于每个 j,我们计算 dp[i - j*j] + 1,表示用 j*j 组成 i 的最少数量。
我们取所有可能的 j 中的最小值,作为 dp[i] 的值。

初始条件:
dp[0] = 0(和为 0 的完全平方数数量为 0)。
例子 2:n = 12
我们需要计算 dp[12],可能的j值:1、2 和 3(因为 1*1 = 1、2*2 = 4 和 3*3 = 9)。

对于 j = 1:
dp[12 - 1*1] + 1 = dp[11] + 1。
假设 dp[11] = 3(即 9 + 1 + 1)。
所以 dp[12] = min(∞, 3 + 1) = 4。

对于 j = 2:
dp[12 - 2*2] + 1 = dp[8] + 1。
假设 dp[8] = 2(即 4 + 4)。
所以 dp[12] = min(4, 2 + 1) = 3。

对于 j = 3:
dp[12 - 3*3] + 1 = dp[3] + 1。
假设 dp[3] = 3(即 1 + 1 + 1)。
所以 dp[12] = min(3, 3 + 1) = 3。

最终 dp[12] = 3(即 4 + 4 + 4)。
public class Solution {
   
   

    // 主方法,用于测试
    public static void main(String[] args) {
   
   
        int n = 12;
        System.out.println(numSquares(n)); // 输出: 3
    }

    // 返回和为 n 的完全平方数的最少数量
    public static int numSquares(int n) {
   
   
        // 初始化 dp 数组
        int[] dp = new int[n + 1];
        Arrays.fill(dp, Integer.MAX_VALUE); // 初始化为最大值
        dp[0] = 0; // 和为 0 的完全平方数数量为 0

        // 动态规划计算 dp[i]
        for (int i = 1; i <= n; i++) {
   
   
            // 遍历所有小于等于 i 的完全平方数
            for (int j = 1; j * j <= i; j++) {
   
   
                dp[i] = Math.min(dp[i], dp[i - j * j] + 1);
            }
        }

        // 返回 dp[n]
        return dp[n];
    }
}

Solution322. 零钱兑换

这里是引用

定义 dp[i] 表示凑成金额 i 所需的最少硬币数量。

初始条件:
dp[0] = 0(凑成金额 0 需要 0 个硬币)。

状态转移方程:
对于每个金额 i,遍历所有硬币面额 coin:
如果 i >= coin,则 dp[i] = min(dp[i], dp[i - coin] + 1)。
最终返回 dp[amount],如果 dp[amount] 仍然是初始值(表示无法凑成),则返回 -1。
执行流程演示
我们以 coins = [1, 2, 5] 和 amount = 11 为例,演示代码的执行流程。

初始状态
dp = [0, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12]

步骤 1: 计算 dp[1]
遍历硬币 1:
dp[1] = min(12, dp[0] + 1) = min(12, 1) = 1
dp = [0, 1, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12]

步骤 2: 计算 dp[2]
遍历硬币 1:
dp[2] = min(12, dp[1] + 1) = min(12, 2) = 2
遍历硬币 2:
dp[2] = min(2, dp[0] + 1) = min(2, 1) = 1
dp = [0, 1, 1, 12, 12, 12, 12, 12, 12, 12, 12, 12]

步骤 3: 计算 dp[3]
遍历硬币 1:
dp[3] = min(12, dp[2] + 1) = min(12, 2) = 2
遍历硬币 2:
dp[3] = min(2, dp[1] + 1) = min(2, 2) = 2
dp = [0, 1, 1, 2, 12, 12, 12, 12, 12, 12, 12, 12]

步骤 4: 计算 dp[4]
遍历硬币 1:
dp[4] = min(12, dp[3] + 1) = min(12, 3) = 3
遍历硬币 2:
dp[4] = min(3, dp[2] + 1) = min(3, 2) = 2
dp = [0, 1, 1, 2, 2, 12, 12, 12, 12, 12, 12, 12]

步骤 5: 计算 dp[5]
遍历硬币 1:
dp[5] = min(12, dp[4] + 1) = min(12, 3) = 3
遍历硬币 2:
dp[5] = min(3, dp[3] + 1) = min(3, 3) = 3
遍历硬币 5:
dp[5] = min(3, dp[0] + 1) = min(3, 1) = 1
dp = [0, 1, 1, 2, 2, 1, 12, 12, 12, 12, 12, 12]

步骤 6: 计算 dp[6]
遍历硬币 1:
dp[6] = min(12, dp[5] + 1) = min(12, 2) = 2
遍历硬币 2:
dp[6] = min(2, dp[4] + 1) = min(2, 3) = 2
遍历硬币 5:
dp[6] = min(2, dp[1] + 1) = min(2, 2) = 2
dp = [0, 1, 1, 2, 2, 1, 2, 12, 12, 12, 12, 12]

步骤 7: 计算 dp[7]
遍历硬币 1:
dp[7] = min(12, dp[6] + 1) = min(12, 3) = 3
遍历硬币 2:
dp[7] = min(3, dp[5] + 1) = min(3, 2) = 2
遍历硬币 5:
dp[7] = min(2, dp[2] + 1) = min(2, 2) = 2
dp = [0, 1, 1, 2, 2, 1, 2, 2, 12, 12, 12, 12]

步骤 8: 计算 dp[8]
遍历硬币 1:
dp[8] = min(12, dp[7] + 1) = min(12, 3) = 3
遍历硬币 2:
dp[8] = min(3, dp[6] + 1) = min(3, 3) = 3
遍历硬币 5:
dp[8] = min(3, dp[3] + 1) = min(3, 3) = 3
dp = [0, 1, 1, 2, 2, 1, 2, 2, 3, 12, 12, 12]

步骤 9: 计算 dp[9]
遍历硬币 1:
dp[9] = min(12, dp[8] + 1) = min(12, 4) = 4
遍历硬币 2:
dp[9] = min(4, dp[7] + 1) = min(4, 3) = 3
遍历硬币 5:
dp[9] = min(3, dp[4] + 1) = min(3, 3) = 3
dp = [0, 1, 1, 2, 2, 1, 2, 2, 3, 3, 12, 12]

步骤 10: 计算 dp[10]
遍历硬币 1:
dp[10] = min(12, dp[9] + 1) = min(12, 4) = 4
遍历硬币 2:
dp[10] = min(4, dp[8] + 1) = min(4, 4) = 4
遍历硬币 5:
dp[10] = min(4, dp[5] + 1) = min(4, 2) = 2
dp = [0, 1, 1, 2, 2, 1, 2, 2, 3, 3, 2, 12]

步骤 11: 计算 dp[11]
遍历硬币 1:
dp[11] = min(12, dp[10] + 1) = min(12, 3) = 3
遍历硬币 2:
dp[11] = min(3, dp[9] + 1) = min(3, 4) = 3
遍历硬币 5:
dp[11] = min(3, dp[6] + 1) = min(3, 3) = 3
dp = [0, 1, 1, 2, 2, 1, 2, 2, 3, 3, 2, 3]

最终状态
dp[11] = 3,即凑成总金额 11 所需的最少硬币数量为 3。

public class Solution {
   
   

    // 主方法,用于测试
    public static void main(String[] args) {
   
   
        int[] coins = {
   
   1, 2, 5};
        int amount = 11;
        System.out.println(coinChange(coins, amount)); // 输出: 3
    }

    // 返回凑成总金额所需的最少硬币数量
    public static int coinChange(int[] coins, int amount) {
   
   
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

BlackTurn

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值