代码随想录第三十七天| 52. 携带研究材料(第七期模拟笔试) 518. 零钱兑换 II 377. 组合总和 Ⅳ 70. 爬楼梯 (进阶)

携带研究材料(第七期模拟笔试)

题目描述

小明是一位科学家,他需要参加一场重要的国际科学大会,以展示自己的最新研究成果。他需要带一些研究材料,但是他的行李箱空间有限。这些研究材料包括实验设备、文献资料和实验样本等等,它们各自占据不同的重量,并且具有不同的价值。

小明的行李箱所能承担的总重量是有限的,问小明应该如何抉择,才能携带最大价值的研究材料。每种研究材料可以选择无数次,并且可以重复选择

解题思路

本题是一个完全背包问题,与0-1 背包问题不同的是,每种物品可以选择无数次。我们可以使用动态规划来求解。

1. 定义状态

dp[i][j] 表示前 i 种物品,背包容量为 j 时能获得的最大价值

2. 状态转移方程

  • 如果当前物品 i 的重量 weight[i] 大于 当前背包容量 j,则不能选择该物品:
    [
    dp[i][j] = dp[i-1][j]
    ]

  • 如果可以选择该物品,我们有两种选择:

    1. 不选该物品,价值等于 dp[i-1][j]
    2. 选择该物品,价值等于 dp[i][j - weight[i]] + value[i](注意这里仍然是 dp[i][...],因为可以重复选择)

    取两者的最大值:
    [
    dp[i][j] = \max(dp[i-1][j], dp[i][j - weight[i]] + value[i])
    ]

3. 边界初始化

  • 只有第一种物品时,dp[0][j] 只依赖于 dp[0][j - weight[0]],按照 weight[0] 递推。

4. 时间复杂度

  • 采用二维 dp 表存储状态,时间复杂度为 O(n \times bagWeight),空间复杂度也是 O(n \times bagWeight)
  • 可以优化为一维数组,从 j=0 开始正向更新 dp[j],这样空间复杂度优化为 O(bagWeight)

Java 代码实现

import java.util.Scanner;

public class Main {
    public static void main(String[] args) {
        Scanner scanner = new Scanner(System.in);
        int n = scanner.nextInt();  // 物品种类
        int bagWeight = scanner.nextInt();  // 背包容量

        int[] weight = new int[n];
        int[] value = new int[n];

        for (int i = 0; i < n; i++) {
            weight[i] = scanner.nextInt();
            value[i] = scanner.nextInt();
        }

        int[][] dp = new int[n][bagWeight + 1];

        // 初始化第一种物品
        for (int j = weight[0]; j <= bagWeight; j++) {
            dp[0][j] = dp[0][j - weight[0]] + value[0];
        }

        // 动态规划
        for (int i = 1; i < n; i++) {
            for (int j = 0; j <= bagWeight; j++) {
                if (j < weight[i]) {
                    dp[i][j] = dp[i - 1][j];
                } else {
                    dp[i][j] = Math.max(dp[i - 1][j], dp[i][j - weight[i]] + value[i]);
                }
            }
        }

        System.out.println(dp[n - 1][bagWeight]);
        scanner.close();
    }
}

代码优化:一维 dp 数组(滚动数组优化)

我们可以只用一维 dp 数组存储状态,从 j=0 开始正向遍历,这样保证物品可重复选择

import java.util.Scanner;

public class Main {
    public static void main(String[] args) {
        Scanner scanner = new Scanner(System.in);
        int n = scanner.nextInt();
        int bagWeight = scanner.nextInt();

        int[] weight = new int[n];
        int[] value = new int[n];

        for (int i = 0; i < n; i++) {
            weight[i] = scanner.nextInt();
            value[i] = scanner.nextInt();
        }

        int[] dp = new int[bagWeight + 1];

        // 遍历物品
        for (int i = 0; i < n; i++) {
            // 这里要正序遍历,确保物品可以被多次选择
            for (int j = weight[i]; j <= bagWeight; j++) {
                dp[j] = Math.max(dp[j], dp[j - weight[i]] + value[i]);
            }
        }

        System.out.println(dp[bagWeight]);
        scanner.close();
    }
}

518. 零钱兑换 II

题目描述

给定不同面额的硬币和一个总金额。写出函数来计算可以凑成总金额的硬币组合数。假设每一种面额的硬币有无限个。

解题思路

本题是一个完全背包问题,与 0-1 背包问题不同的是,每种物品(硬币)可以选择无限次。

1. 定义状态

dp[j] 表示凑成金额 j 的硬币组合数

2. 状态转移方程

  • 对于每个硬币 coins[i],遍历所有金额 j(从 coins[i]amount)。
  • 状态转移方程:
    [
    dp[j] = dp[j] + dp[j - coins[i]]
    ]
    • dp[j]:不使用当前硬币 coins[i] 时,凑成 j 的组合数。
    • dp[j - coins[i]]:使用当前硬币 coins[i] 后,凑成 j 的组合数。

3. 初始化

  • dp[0] = 1,表示凑成金额 0 的方案数为 1(不使用任何硬币)。
  • 其余 dp[j] 初始化为 0

4. 遍历顺序

  • 外层遍历硬币种类,确保每种硬币可以被多次选择。
  • 内层遍历金额,从 coins[i] 开始递增,保证组合数不会重复计算。

5. 时间复杂度

  • O(n \times amount),其中 n 是硬币种类数,amount 是目标金额。
  • 空间复杂度 O(amount),只使用一维 dp 数组。

Java 代码实现

class Solution {
    public int change(int amount, int[] coins) {
        int[] dp = new int[amount+1];
        dp[0] = 1;
        
        for (int i = 0; i < coins.length; i++) {
            for (int j = coins[i]; j <= amount; j++) {
                dp[j] = dp[j] + dp[j - coins[i]];
            }
        }
        
        return dp[amount];
    }
}

组合总和 IV

题目描述

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

解题思路

本题属于完全背包问题,不同点在于:

  • 组合问题:顺序不同的组合视为不同的方案。
  • 完全背包:每个数可以被多次使用。

1. 定义状态

dp[j] 表示凑成目标和 j 的组合数

2. 状态转移方程

对于每个 j,遍历 nums 中的所有元素 nums[i],如果 j >= nums[i],则:
[
dp[j] += dp[j - nums[i]]
]

  • dp[j - nums[i]] 代表去掉 nums[i] 之前的方案数。
  • dp[j] 通过遍历 nums[i] 进行累加。

3. 初始化

  • dp[0] = 1,表示凑成 0 的唯一方式是选取空集合
  • 其余 dp[j] 初始化为 0

4. 遍历顺序

  • 先遍历 j(目标和),再遍历 nums[i]
  • 保证不同顺序的组合能被计算到
  • 由于顺序不同的组合视为不同方案,因此需要先遍历目标和,再遍历数组

5. 时间复杂度

  • O(target × n),其中 nnums 的大小。
  • 空间复杂度 O(target),使用一维 dp 数组。

Java 代码实现

class Solution {
    public int combinationSum4(int[] nums, int target) {
        int[] dp = new int[target + 1];
        dp[0] = 1;
        
        for (int j = 0; j <= target; j++) {
            for (int i = 0; i < nums.length; i++) {
                if (j >= nums[i]) {
                    dp[j] += dp[j - nums[i]];
                }
            }
        }
        
        return dp[target];
    }
}

爬楼梯问题

题目描述

假设你正在爬楼梯。需要 n 阶才能到达楼顶。

每次你可以爬 12 个台阶。请问有多少种不同的方法可以爬到楼顶?

解题思路

本题是一个动态规划(DP)问题,类似于斐波那契数列。

1. 定义状态

dp[j] 表示爬到第 j 阶的方法数

2. 状态转移方程

  • 每次可以爬 12 个台阶,因此:
    [
    dp[j] = dp[j-1] + dp[j-2]
    ]
  • dp[j-1] 代表最后一步爬 1 阶的情况。
  • dp[j-2] 代表最后一步爬 2 阶的情况。

3. 初始化

  • dp[0] = 1(空楼梯只有一种方式,不动)。
  • dp[1] = 1(爬 1 阶只能有 1 种方式)。
  • dp[2] = 2(可以走 1+12)。

4. 遍历顺序

  • 由于 dp[j] 依赖于 dp[j-1]dp[j-2],需从小到大遍历 j

5. 时间复杂度

  • O(n),因为 dp 数组需要遍历 n 次。
  • 空间复杂度 O(n),可以进一步优化到 O(1) 使用滚动变量。

Java 代码实现

class Solution {
    public int climbStairs(int n) {
        if (n < 3) return n;
        int[] dp = new int[n + 1];
        dp[0] = 1;
        
        for (int j = 0; j <= n; j++) {
            for (int i = 1; i < 3; i++) {
                if (j >= i) dp[j] += dp[j - i];
            }
        }
        
        return dp[n];
    }
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值