題目:
美國郵票的面值共有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); } } }