题目:零钱兑换
动态规划:dfs是自顶而下的递归,dp是自底而上的递推。动态规划将复杂问题拆分成若干子问题,每个子问题仅仅解决一次,并保存子问题的解,最后导出原问题的解。
用动规解决的问题通常具备以下两个特征:
1.最优子结构(最优化原理):通过求解子问题的最优解,可以获得原问题的最优解。
2.无后效性:
√ 某阶段的状态一旦确定,则此后过程的演变不再受此前各状态及决策的影响(未来与过去无关)
√ 在推导后面阶段的状态时,只关心前面状态的具体值,不关心该状态时怎么演变过来的
动态规划步骤:
1.定义状态:如dp[i]的含义
2.设置初始状态(边界):如dp[ 0 ]的值
3.确定状态转移方程:如dp[ i ]和dp[ i-1 ]的关系
题目描述:
假设有25分,20分,5分,1分的硬币,硬币数量不限,现要找给客户41分的零钱,如何办到使用硬币数量最少?
解析:
定义dp[n]为找给客户n分零钱需要的硬币最少。
假设第一次选择25分的,那么dp[n] = dp[n-25] + 1;
假设第一次选择20分的,那么dp[n] = dp[n-20] + 1;
假设第一次选择5分的, 那么dp[n] = dp[n-5] + 1;
假设第一次选择1分的, 那么dp[n] = dp[n-1] + 1;
在这四种情况中选出需要硬币最少的,这就确定了状态转移方程:
dp[n] = min{ dp[n-25], dp[n-20], dp[n-5],dp[n-1] } + 1;
方式一:
public class _01_零钱兑换_dp {
public static void main(String[] args) {
Scanner in = new Scanner(System.in);
int N = in.nextInt(); //需要兑换的零钱
in.close();
int[] dp = new int[N + 1];
for (int i = 1; i <= N; i++) { //dp[i]:兑换i分零钱需要的最少硬币
int min = Integer.MAX_VALUE;
if(i >= 1) min = Math.min(dp[i] = dp[i-1], min);
if(i >= 5) min = Math.min(dp[i] = dp[i-5], min);
if(i >= 20) min = Math.min(dp[i] = dp[i-20], min);
if(i >= 25) min = Math.min(dp[i] = dp[i-25], min);
dp[i] = min + 1;
}
System.out.println(dp[N]);
}
}
方式二:
public class _零钱兑换 {
public static void main(String[] args) {
Scanner in = new Scanner(System.in);
int N = in.nextInt(); //录入零钱
in.close();
int[] faces = {1, 5, 20, 25};
int[] dp = new int [N + 1];
for (int i = 1; i <= N; i++) {
int min = Integer.MAX_VALUE;
for (int face : faces) {
if (face > i) continue; // 给定的面值比要凑的面值还大, 跳过此轮循环
min = Math.min(dp[i - face], min);
}
dp[i] = min + 1;
}
System.out.println(dp[N]);
}
}
两种方式本质上并无差别。