题目:
美国邮票的面值共有1, 10, 21, 34, 70, 100, 350, 1225, 1500九种(单位为美分)。现给定一个邮资的价格n(以美分为单位),如果规定所贴邮票面值总和必须等于n,请输出最少要贴几张邮票。为了简化程序,我们假设只有1, 10, 21, 34四种面值。
输入格式:
为一个整数n。(0<n<1000)。测试用例保证合法且均可以用int存储。
输出格式:
也是一个整数,为所贴邮票的张数。
输入样例:
22
输出样例:
2
分析:
1、动态规划:
初始化——
1 int[] dp = new int[n+1]; 2 for (int i = 1; i <= n; i++) { 3 dp[i] = i; 4 }
int[] dp: 存储计算结果,防止重复计算.
dp[i]:组成总面值为 i 的邮票数,因为邮票数>=1,为了不影响状态转移方程的计算,初始化的dp[i]应该取得它可以取到的最大值或更大,所以取dp[i] = i 或更大,即邮票面值为i时邮票数最多是i. 且题目0 < n < 1000,取不到0,也就无所谓i 从1开始还是从0开始.
状态转移方程——
1 for (int i = 1; i <= n; i++) { 2 for (int stamp : stampsPrice) { 3 if (i >= stamp) { 4 dp[i] = Math.min(dp[i], dp[i - stamp] + 1);//状态转移方程 5 } 6 } 7 }
第一个循环计算出每个dp[i]的值,
dp[i] = Math.min(dp[i], dp[i - stamp] + 1);动态规划的核心,更新dp[i],需要判断面值是否大于或等于stamp,因为面值小于stamp会导致dp[i - stamp]超出数组范围,1是那一张stamp的数量
2、回溯算法
public class stamp {public static void main(String[] args) {int[] stamps = {1, 10, 21, 34}; // 邮票面值int target = 5; // 目标邮资System.out.println("最少需要邮票数量: " + minStamps(stamps, target));}public static int minStamps(int[] stamps, int target) {int[] minCount = {1000}; // 初始化最小邮票数量backtrack(stamps, target, 0, 0, minCount); // 调用回溯函数return minCount[0];}public static void backtrack(int[] stamps, int remain, int count, int start, int[] minCount) {if (remain == 0) { // 如果剩余面值为0,表示找到一种组合minCount[0] = Math.min(minCount[0], count); // 更新最小邮票数量return;}if (remain < 0 || start == stamps.length) { // 如果剩余面值小于0或者已经遍历完所有面值,直接返回return;}for (int i = start; i < stamps.length; i++) { // 从当前面值开始尝试if (count + 1 < minCount[0]) { // 剪枝:如果当前邮票数量已经大于最小数量,直接返回backtrack(stamps, remain - stamps[i], count + 1, i, minCount);} else {return;}}} }
3、分支定界法
public class stamp {public static void main(String[] args) {int[] stamps = {1, 10, 21, 34}; // 邮票面值int target = 22; // 目标邮资System.out.println("最少需要邮票数量: " + minStamps(stamps, target));}public static int minStamps(int[] stamps, int target) {Arrays.sort(stamps); // 将邮票面值排序,方便进行剪枝int[] minCount = {1000}; // 初始化最小邮票数量backtrack(stamps, target, 0, 0, minCount); // 调用回溯函数return minCount[0];}public static void backtrack(int[] stamps, int remain, int count, int start, int[] minCount) {if (remain == 0) { // 如果剩余面值为0,表示找到一种组合minCount[0] = Math.min(minCount[0], count); // 更新最小邮票数量return;}if (start == stamps.length || remain < 0) { // 如果已经遍历完所有面值或者剩余面值小于0,直接返回return;}// 计算当前面值能够使用的最大数量,即剩余面值除以当前面值int maxCount = remain / stamps[start];// 剪枝:如果当前邮票数量加上剩余面值除以当前面值的最大数量仍然大于等于当前最小数量,直接返回if (count + maxCount >= minCount[0]) {return;}// 逐个尝试当前面值可使用的数量,并递归搜索for (int i = maxCount; i >= 0; i--) {backtrack(stamps, remain - i * stamps[start], count + i, start + 1, minCount);}} }