力扣热题100-----322.零钱兑换
案例
给你一个整数数组 coins ,表示不同面额的硬币;以及一个整数 amount ,表示总金额。
计算并返回可以凑成总金额所需的 最少的硬币个数 。如果没有任何一种硬币组合能组成总金额,返回 -1 。
你可以认为每种硬币的数量是无限的。
示例 1:
输入:coins = [1, 2, 5], amount = 11
输出:3
解释:11 = 5 + 5 + 1
示例 2:
输入:coins = [2], amount = 3
输出:-1
示例 3:
输入:coins = [1], amount = 0
输出:0
提示:
1 <= coins.length <= 12
1 <= coins[i] <= 231 - 1
0 <= amount <= 104
动态规划思路
1.状态定义
定义nums[i]
为凑成金额i
所需的最少硬币数量。
2.状态初始化
• nums[0] = 0
:凑成金额0
不需要任何硬币。
• 对于其他金额i
,初始值设为amount + 1
。这是一个足够大的数,表示初始时无法凑成这些金额。
3.状态转移方程
对于每个金额i
(从1
到amount
),尝试使用每种硬币coin
:
• 如果当前金额i
大于等于硬币面额coin
,则可以考虑使用这个硬币。
• 更新nums[i]
的值为:
[
nums[i]=\min(nums[i],nums[i-coin]+1)
]
这里的nums[i - coin] + 1
表示在凑成金额i - coin
的基础上,再加一个面额为coin
的硬币。
4.最终结果
• 如果nums[amount]
仍然是初始值amount + 1
,说明无法凑成该金额,返回-1
。
• 否则,返回nums[amount]
,即凑成金额amount
所需的最少硬币数量。
class Solution {public int coinChange(int[] coins, int amount) {int[] nums=new int[amount+1];for(int i=0;i<=amount;i++){nums[i]=amount+1;}nums[0]=0;for(int i=1;i<=amount;i++){for(int coin:coins){if(i>=coin){nums[i]=Math.min(nums[i],nums[i-coin]+1);}}}return nums[amount]==amount+1 ? -1:nums[amount];}
}
动态规划法:
动态规划法介绍:
动态规划(Dynamic Programming,简称 DP)是一种用于解决多阶段决策问题的算法思想,它通过将复杂问题分解为更简单的子问题,并存储这些子问题的解以避免重复计算,从而高效地解决问题。动态规划通常用于优化问题,尤其是那些具有重叠子问题和最优子结构特性的问题。
核心概念
• 最优子结构:
• 一个问题的最优解可以由其子问题的最优解组合而成。换句话说,问题的解可以分解为若干个子问题的解。
• 例如,在爬楼梯问题中,到达第(n)阶的方法数可以由到达第(n-1)阶和第(n-2)阶的方法数组合而成。
• 重叠子问题:
• 在递归求解过程中,同一个子问题会被多次计算。动态规划通过存储这些子问题的解(通常使用一个数组或哈希表),避免重复计算,从而提高效率。
• 例如,在递归计算斐波那契数列时,会多次计算相同的值,而动态规划通过存储这些值来避免重复计算。
动态规划的优势
• 高效性:
• 动态规划通过存储子问题的解,避免了重复计算,大大提高了算法的效率。时间复杂度通常为(O(n))。
• 适用性:
• 动态规划适用于具有重叠子问题和最优子结构特性的问题,如背包问题、最长公共子序列、最短路径问题等。
• 可扩展性:
• 动态规划的思想可以扩展到多维问题,通过增加状态维度来解决更复杂的问题。
动态规划的局限性
• 空间复杂度:
• 动态规划通常需要额外的空间来存储子问题的解,空间复杂度可能较高。例如,爬楼梯问题的空间复杂度为(O(n))。
• 状态转移方程的推导:
• 动态规划的关键在于推导状态转移方程,这需要对问题有深入的理解和分析。
• 适用范围:
• 动态规划只适用于具有重叠子问题和最优子结构特性的问题,对于不符合这些特性的问题,动态规划可能不适用。
总结
动态规划是一种强大的算法思想,通过将复杂问题分解为更简单的子问题,并存储这些子问题的解,避免重复计算,从而高效地解决问题。爬楼梯问题是动态规划的经典应用之一,通过定义状态、初始化状态、状态转移和计算顺序,可以高效地求解问题。