leetcode
leetcode 2951 ~ 3000
零钱兑换

零钱兑换

难度:

标签:

题目描述

English description is not available for the problem. Please switch to Chinese.

代码结果

运行时间: 64 ms, 内存: 16.0 MB


/*
 * 思路:
 * 1. 使用动态规划的方法,定义一个 dp 数组,dp[i] 表示凑成金额 i 所需的最少硬币个数。
 * 2. 使用 IntStream 和 Arrays.stream 来实现动态规划的步骤。
 * 3. 初始化 dp[0] = 0,表示凑成金额 0 所需的硬币个数为 0。
 * 4. 对于每个金额 i,从 1 到 amount,尝试用每个硬币去更新 dp[i]。
 * 5. 如果当前硬币的面值不大于 i,则 dp[i] = Math.min(dp[i], dp[i - coin] + 1)。
 * 6. 最后,返回 dp[amount],如果 dp[amount] 仍然是初始值,则返回 -1 表示无法凑成该金额。
 */
import java.util.*;
import java.util.stream.*;

public class Solution {
    public int coinChange(int[] coins, int amount) {
        int[] dp = new int[amount + 1];
        Arrays.fill(dp, amount + 1);
        dp[0] = 0;
        IntStream.rangeClosed(1, amount).forEach(i -> {
            Arrays.stream(coins).forEach(coin -> {
                if (coin <= i) {
                    dp[i] = Math.min(dp[i], dp[i - coin] + 1);
                }
            });
        });
        return dp[amount] > amount ? -1 : dp[amount];
    }
}

解释

方法:

题解中采用了一种基于位运算的动态规划方法来解决零钱兑换问题。首先,使用一个整数`dp`的位表示,其中第i位的值为1表示能够用硬币组合凑成金额i。初始化`dp`为1左移`amount`位,表示只有amount位为1,其余均为0。使用一个无限循环,每次循环中检查第0位,如果为1,则返回当前的步数(已经找到最小硬币组合)。对于每种硬币面值,将`dp`右移该硬币面值的位数,通过位或操作更新`dp`,如果在某次循环中`dp`未发生变化,说明无法通过已有硬币组合凑出更多金额,此时返回-1。

时间复杂度:

O(amount * k)

空间复杂度:

O(1)

代码细节讲解

🦆
在题解中使用位运算和动态规划结合的方法较为少见,你能解释为什么选择使用位运算来表示状态而不是更常见的数组或哈希表吗?
在本题解中,使用位运算来表示状态而不是数组或哈希表主要是为了利用位运算的高效性和紧凑性。位运算允许我们在单个整数中存储和操作多个状态,这样可以减少内存的占用,并且位运算的操作(如位移和位或)通常比数组或哈希表的操作更快。同时,位运算还能简化某些逻辑,如状态转移,因为可以通过位移和位或直接实现状态的更新,而不需要逐个元素的迭代处理。这种方法尤其适用于涉及连续整数范围的问题,如本问题中的金额表示。
🦆
题解中提到,如果`dp`在某次循环后未发生变化则返回-1。在什么情况下会出现`dp`未变化的情况,能否给出一个具体的例子?
如果在某次循环中`dp`未发生变化,意味着通过当前的硬币面值组合无法生成新的金额。这通常发生在硬币的面值无法组合出介于已有金额之外的新金额时。例如,假设有硬币面值为2和5,要凑的金额是3。初始`dp`设置为第3位为1(表示目标是3),但由于无论如何组合2和5都无法凑出3(2和5都无法减少到1),所以每次更新`dp`后,状态不会包括第0位为1的情况,因此`dp`最终不会变化,算法就会返回-1,表示无法凑出目标金额。
🦆
初始化`dp`为1左移`amount`位,这种初始化方式有什么特别的意义吗?为什么选择将第`amount`位设置为1而其他位为0?
初始化`dp`为1左移`amount`位是一种表示目标金额可以通过0枚硬币直接达到的方法。在这种初始化方式中,将第`amount`位设置为1而其他位为0意味着起始状态只考虑能通过硬币组合凑出金额`amount`的可能性,而忽略其他所有金额。这样的设置允许算法从特定的目标金额开始向下计算,每次通过硬币面值的位移来逐步检查能否凑出较小的金额。这是一种从目标金额向零金额“倒推”的方法,与传统的动态规划从0开始向目标金额累加的方式相反。

相关问题