问题描述
现在有 M 种不同面值的硬币(硬币个数数目无限),如何用这些硬币组合为总数额为 N 的钱,同时满足使用硬币的个数最少?
测试样例
输入样例
[1,2,5]
18
输出样例
[5,5,5,2,1]
解决思路
我们需要找到一种组合,使得使用给定的硬币面值组合成目标金额 N,并且使用的硬币数量最少。
数据结构选择
我们可以使用一个数组 dp,其中 dp[i] 表示组成金额 i 所需的最少硬币数量。
算法步骤
-
初始化:
- 创建一个大小为
amount + 1的数组dp,并将所有元素初始化为一个较大的值(例如amount + 1),表示初始状态下无法组成该金额。 - 将
dp[0]设置为0,因为组成金额0不需要任何硬币。
- 创建一个大小为
-
状态转移:
- 对于每个金额
i(从1到amount),遍历所有硬币面值coin。 - 如果
coin小于或等于i,则更新dp[i]为min(dp[i], dp[i - coin] + 1)。
- 对于每个金额
-
结果:
- 如果
dp[amount]仍然是一个较大的值(例如amount + 1),则说明无法组成目标金额,返回空数组。 - 否则,从
dp[amount]开始,逆向追踪硬币组合,构建结果数组。
- 如果
代码实现
public static int solution(int n, int m, String str1, String str2) {
int maxLen = 0;
// 遍历所有可能的字符 'a' 到 'z'
for (char targetChar = 'a'; targetChar <= 'z'; targetChar++) {
int left = 0, right = 0;
int repairs = 0;
while (right < n) {
// 如果当前字符不是目标字符,且可以修改
if (str1.charAt(right) != targetChar && str2.charAt(right) == '1') {
repairs++;
}
// 如果当前字符不是目标字符,且不可修改,则重置窗口
if (str1.charAt(right) != targetChar && str2.charAt(right) == '0') {
left = right + 1;
repairs = 0;
}
// 如果修复次数超过 m,移动左指针
while (repairs > m) {
if (str1.charAt(left) != targetChar && str2.charAt(left) == '1') {
repairs--;
}
left++;
}
// 计算当前窗口的长度
maxLen = Math.max(maxLen, right - left + 1);
right++;
}
}
return maxLen;
}
动态规划算法的使用建议
动态规划适用于具有重叠子问题和最优子结构的场景,特别是当问题可以分解成多个子问题,且子问题的解可以通过递推得到时。在使用时,首先要明确状态表示、状态转移方程,并选择合适的存储方式(如自底向上或自顶向下的计算方法)。此外,要注意优化空间复杂度,避免不必要的计算和空间浪费。动态规划的核心思想是通过保存子问题的解来避免重复计算,从而提高效率。通常,动态规划有两种常见的实现方式:自顶向下(递归+记忆化)和自底向上(迭代)。在实现时,首先要明确问题的状态表示,即如何将问题转化为子问题的解,然后定义状态转移方程,确保每个子问题的解只计算一次并存储。动态规划常用于求解如最短路径、最大子数组和背包问题等典型问题,适合处理大规模的问题,但也要注意防止状态空间过大导致的时间和空间复杂度问题。