--- comments: true --- # 14.5   Unbounded knapsack problem In this section, we first solve another common knapsack problem: the unbounded knapsack, and then explore a special case of it: the coin change problem. ## 14.5.1   Unbounded knapsack problem !!! question Given $n$ items, where the weight of the $i^{th}$ item is $wgt[i-1]$ and its value is $val[i-1]$, and a backpack with a capacity of $cap$. **Each item can be selected multiple times**. What is the maximum value of the items that can be put into the backpack without exceeding its capacity? See the example below. ![Example data for the unbounded knapsack problem](unbounded_knapsack_problem.assets/unbounded_knapsack_example.png){ class="animation-figure" }

Figure 14-22   Example data for the unbounded knapsack problem

### 1.   Dynamic programming approach The unbounded knapsack problem is very similar to the 0-1 knapsack problem, **the only difference being that there is no limit on the number of times an item can be chosen**. - In the 0-1 knapsack problem, there is only one of each item, so after placing item $i$ into the backpack, you can only choose from the previous $i-1$ items. - In the unbounded knapsack problem, the quantity of each item is unlimited, so after placing item $i$ in the backpack, **you can still choose from the previous $i$ items**. Under the rules of the unbounded knapsack problem, the state $[i, c]$ can change in two ways. - **Not putting item $i$ in**: As with the 0-1 knapsack problem, transition to $[i-1, c]$. - **Putting item $i$ in**: Unlike the 0-1 knapsack problem, transition to $[i, c-wgt[i-1]]$. The state transition equation thus becomes: $$ dp[i, c] = \max(dp[i-1, c], dp[i, c - wgt[i-1]] + val[i-1]) $$ ### 2.   Code implementation Comparing the code for the two problems, the state transition changes from $i-1$ to $i$, the rest is completely identical: === "Python" ```python title="unbounded_knapsack.py" def unbounded_knapsack_dp(wgt: list[int], val: list[int], cap: int) -> int: """完全背包:动态规划""" n = len(wgt) # 初始化 dp 表 dp = [[0] * (cap + 1) for _ in range(n + 1)] # 状态转移 for i in range(1, n + 1): for c in range(1, cap + 1): if wgt[i - 1] > c: # 若超过背包容量,则不选物品 i dp[i][c] = dp[i - 1][c] else: # 不选和选物品 i 这两种方案的较大值 dp[i][c] = max(dp[i - 1][c], dp[i][c - wgt[i - 1]] + val[i - 1]) return dp[n][cap] ``` === "C++" ```cpp title="unbounded_knapsack.cpp" /* 完全背包:动态规划 */ int unboundedKnapsackDP(vector &wgt, vector &val, int cap) { int n = wgt.size(); // 初始化 dp 表 vector> dp(n + 1, vector(cap + 1, 0)); // 状态转移 for (int i = 1; i <= n; i++) { for (int c = 1; c <= cap; c++) { if (wgt[i - 1] > c) { // 若超过背包容量,则不选物品 i dp[i][c] = dp[i - 1][c]; } else { // 不选和选物品 i 这两种方案的较大值 dp[i][c] = max(dp[i - 1][c], dp[i][c - wgt[i - 1]] + val[i - 1]); } } } return dp[n][cap]; } ``` === "Java" ```java title="unbounded_knapsack.java" /* 完全背包:动态规划 */ int unboundedKnapsackDP(int[] wgt, int[] val, int cap) { int n = wgt.length; // 初始化 dp 表 int[][] dp = new int[n + 1][cap + 1]; // 状态转移 for (int i = 1; i <= n; i++) { for (int c = 1; c <= cap; c++) { if (wgt[i - 1] > c) { // 若超过背包容量,则不选物品 i dp[i][c] = dp[i - 1][c]; } else { // 不选和选物品 i 这两种方案的较大值 dp[i][c] = Math.max(dp[i - 1][c], dp[i][c - wgt[i - 1]] + val[i - 1]); } } } return dp[n][cap]; } ``` === "C#" ```csharp title="unbounded_knapsack.cs" /* 完全背包:动态规划 */ int UnboundedKnapsackDP(int[] wgt, int[] val, int cap) { int n = wgt.Length; // 初始化 dp 表 int[,] dp = new int[n + 1, cap + 1]; // 状态转移 for (int i = 1; i <= n; i++) { for (int c = 1; c <= cap; c++) { if (wgt[i - 1] > c) { // 若超过背包容量,则不选物品 i dp[i, c] = dp[i - 1, c]; } else { // 不选和选物品 i 这两种方案的较大值 dp[i, c] = Math.Max(dp[i - 1, c], dp[i, c - wgt[i - 1]] + val[i - 1]); } } } return dp[n, cap]; } ``` === "Go" ```go title="unbounded_knapsack.go" /* 完全背包:动态规划 */ func unboundedKnapsackDP(wgt, val []int, cap int) int { n := len(wgt) // 初始化 dp 表 dp := make([][]int, n+1) for i := 0; i <= n; i++ { dp[i] = make([]int, cap+1) } // 状态转移 for i := 1; i <= n; i++ { for c := 1; c <= cap; c++ { if wgt[i-1] > c { // 若超过背包容量,则不选物品 i dp[i][c] = dp[i-1][c] } else { // 不选和选物品 i 这两种方案的较大值 dp[i][c] = int(math.Max(float64(dp[i-1][c]), float64(dp[i][c-wgt[i-1]]+val[i-1]))) } } } return dp[n][cap] } ``` === "Swift" ```swift title="unbounded_knapsack.swift" /* 完全背包:动态规划 */ func unboundedKnapsackDP(wgt: [Int], val: [Int], cap: Int) -> Int { let n = wgt.count // 初始化 dp 表 var dp = Array(repeating: Array(repeating: 0, count: cap + 1), count: n + 1) // 状态转移 for i in 1 ... n { for c in 1 ... cap { if wgt[i - 1] > c { // 若超过背包容量,则不选物品 i dp[i][c] = dp[i - 1][c] } else { // 不选和选物品 i 这两种方案的较大值 dp[i][c] = max(dp[i - 1][c], dp[i][c - wgt[i - 1]] + val[i - 1]) } } } return dp[n][cap] } ``` === "JS" ```javascript title="unbounded_knapsack.js" /* 完全背包:动态规划 */ function unboundedKnapsackDP(wgt, val, cap) { const n = wgt.length; // 初始化 dp 表 const dp = Array.from({ length: n + 1 }, () => Array.from({ length: cap + 1 }, () => 0) ); // 状态转移 for (let i = 1; i <= n; i++) { for (let c = 1; c <= cap; c++) { if (wgt[i - 1] > c) { // 若超过背包容量,则不选物品 i dp[i][c] = dp[i - 1][c]; } else { // 不选和选物品 i 这两种方案的较大值 dp[i][c] = Math.max( dp[i - 1][c], dp[i][c - wgt[i - 1]] + val[i - 1] ); } } } return dp[n][cap]; } ``` === "TS" ```typescript title="unbounded_knapsack.ts" /* 完全背包:动态规划 */ function unboundedKnapsackDP( wgt: Array, val: Array, cap: number ): number { const n = wgt.length; // 初始化 dp 表 const dp = Array.from({ length: n + 1 }, () => Array.from({ length: cap + 1 }, () => 0) ); // 状态转移 for (let i = 1; i <= n; i++) { for (let c = 1; c <= cap; c++) { if (wgt[i - 1] > c) { // 若超过背包容量,则不选物品 i dp[i][c] = dp[i - 1][c]; } else { // 不选和选物品 i 这两种方案的较大值 dp[i][c] = Math.max( dp[i - 1][c], dp[i][c - wgt[i - 1]] + val[i - 1] ); } } } return dp[n][cap]; } ``` === "Dart" ```dart title="unbounded_knapsack.dart" /* 完全背包:动态规划 */ int unboundedKnapsackDP(List wgt, List val, int cap) { int n = wgt.length; // 初始化 dp 表 List> dp = List.generate(n + 1, (index) => List.filled(cap + 1, 0)); // 状态转移 for (int i = 1; i <= n; i++) { for (int c = 1; c <= cap; c++) { if (wgt[i - 1] > c) { // 若超过背包容量,则不选物品 i dp[i][c] = dp[i - 1][c]; } else { // 不选和选物品 i 这两种方案的较大值 dp[i][c] = max(dp[i - 1][c], dp[i][c - wgt[i - 1]] + val[i - 1]); } } } return dp[n][cap]; } ``` === "Rust" ```rust title="unbounded_knapsack.rs" /* 完全背包:动态规划 */ fn unbounded_knapsack_dp(wgt: &[i32], val: &[i32], cap: usize) -> i32 { let n = wgt.len(); // 初始化 dp 表 let mut dp = vec![vec![0; cap + 1]; n + 1]; // 状态转移 for i in 1..=n { for c in 1..=cap { if wgt[i - 1] > c as i32 { // 若超过背包容量,则不选物品 i dp[i][c] = dp[i - 1][c]; } else { // 不选和选物品 i 这两种方案的较大值 dp[i][c] = std::cmp::max(dp[i - 1][c], dp[i][c - wgt[i - 1] as usize] + val[i - 1]); } } } return dp[n][cap]; } ``` === "C" ```c title="unbounded_knapsack.c" /* 完全背包:动态规划 */ int unboundedKnapsackDP(int wgt[], int val[], int cap, int wgtSize) { int n = wgtSize; // 初始化 dp 表 int **dp = malloc((n + 1) * sizeof(int *)); for (int i = 0; i <= n; i++) { dp[i] = calloc(cap + 1, sizeof(int)); } // 状态转移 for (int i = 1; i <= n; i++) { for (int c = 1; c <= cap; c++) { if (wgt[i - 1] > c) { // 若超过背包容量,则不选物品 i dp[i][c] = dp[i - 1][c]; } else { // 不选和选物品 i 这两种方案的较大值 dp[i][c] = myMax(dp[i - 1][c], dp[i][c - wgt[i - 1]] + val[i - 1]); } } } int res = dp[n][cap]; // 释放内存 for (int i = 0; i <= n; i++) { free(dp[i]); } return res; } ``` === "Kotlin" ```kotlin title="unbounded_knapsack.kt" /* 完全背包:动态规划 */ fun unboundedKnapsackDP(wgt: IntArray, _val: IntArray, cap: Int): Int { val n = wgt.size // 初始化 dp 表 val dp = Array(n + 1) { IntArray(cap + 1) } // 状态转移 for (i in 1..n) { for (c in 1..cap) { if (wgt[i - 1] > c) { // 若超过背包容量,则不选物品 i dp[i][c] = dp[i - 1][c] } else { // 不选和选物品 i 这两种方案的较大值 dp[i][c] = max(dp[i - 1][c], dp[i][c - wgt[i - 1]] + _val[i - 1]) } } } return dp[n][cap] } ``` === "Ruby" ```ruby title="unbounded_knapsack.rb" [class]{}-[func]{unbounded_knapsack_dp} ``` === "Zig" ```zig title="unbounded_knapsack.zig" // 完全背包:动态规划 fn unboundedKnapsackDP(comptime wgt: []i32, val: []i32, comptime cap: usize) i32 { comptime var n = wgt.len; // 初始化 dp 表 var dp = [_][cap + 1]i32{[_]i32{0} ** (cap + 1)} ** (n + 1); // 状态转移 for (1..n + 1) |i| { for (1..cap + 1) |c| { if (wgt[i - 1] > c) { // 若超过背包容量,则不选物品 i dp[i][c] = dp[i - 1][c]; } else { // 不选和选物品 i 这两种方案的较大值 dp[i][c] = @max(dp[i - 1][c], dp[i][c - @as(usize, @intCast(wgt[i - 1]))] + val[i - 1]); } } } return dp[n][cap]; } ``` ??? pythontutor "Code Visualization"
### 3.   Space optimization Since the current state comes from the state to the left and above, **the space-optimized solution should perform a forward traversal for each row in the $dp$ table**. This traversal order is the opposite of that for the 0-1 knapsack. Please refer to Figure 14-23 to understand the difference. === "<1>" ![Dynamic programming process for the unbounded knapsack problem after space optimization](unbounded_knapsack_problem.assets/unbounded_knapsack_dp_comp_step1.png){ class="animation-figure" } === "<2>" ![unbounded_knapsack_dp_comp_step2](unbounded_knapsack_problem.assets/unbounded_knapsack_dp_comp_step2.png){ class="animation-figure" } === "<3>" ![unbounded_knapsack_dp_comp_step3](unbounded_knapsack_problem.assets/unbounded_knapsack_dp_comp_step3.png){ class="animation-figure" } === "<4>" ![unbounded_knapsack_dp_comp_step4](unbounded_knapsack_problem.assets/unbounded_knapsack_dp_comp_step4.png){ class="animation-figure" } === "<5>" ![unbounded_knapsack_dp_comp_step5](unbounded_knapsack_problem.assets/unbounded_knapsack_dp_comp_step5.png){ class="animation-figure" } === "<6>" ![unbounded_knapsack_dp_comp_step6](unbounded_knapsack_problem.assets/unbounded_knapsack_dp_comp_step6.png){ class="animation-figure" }

Figure 14-23   Dynamic programming process for the unbounded knapsack problem after space optimization

The code implementation is quite simple, just remove the first dimension of the array `dp`: === "Python" ```python title="unbounded_knapsack.py" def unbounded_knapsack_dp_comp(wgt: list[int], val: list[int], cap: int) -> int: """完全背包:空间优化后的动态规划""" n = len(wgt) # 初始化 dp 表 dp = [0] * (cap + 1) # 状态转移 for i in range(1, n + 1): # 正序遍历 for c in range(1, cap + 1): if wgt[i - 1] > c: # 若超过背包容量,则不选物品 i dp[c] = dp[c] else: # 不选和选物品 i 这两种方案的较大值 dp[c] = max(dp[c], dp[c - wgt[i - 1]] + val[i - 1]) return dp[cap] ``` === "C++" ```cpp title="unbounded_knapsack.cpp" /* 完全背包:空间优化后的动态规划 */ int unboundedKnapsackDPComp(vector &wgt, vector &val, int cap) { int n = wgt.size(); // 初始化 dp 表 vector dp(cap + 1, 0); // 状态转移 for (int i = 1; i <= n; i++) { for (int c = 1; c <= cap; c++) { if (wgt[i - 1] > c) { // 若超过背包容量,则不选物品 i dp[c] = dp[c]; } else { // 不选和选物品 i 这两种方案的较大值 dp[c] = max(dp[c], dp[c - wgt[i - 1]] + val[i - 1]); } } } return dp[cap]; } ``` === "Java" ```java title="unbounded_knapsack.java" /* 完全背包:空间优化后的动态规划 */ int unboundedKnapsackDPComp(int[] wgt, int[] val, int cap) { int n = wgt.length; // 初始化 dp 表 int[] dp = new int[cap + 1]; // 状态转移 for (int i = 1; i <= n; i++) { for (int c = 1; c <= cap; c++) { if (wgt[i - 1] > c) { // 若超过背包容量,则不选物品 i dp[c] = dp[c]; } else { // 不选和选物品 i 这两种方案的较大值 dp[c] = Math.max(dp[c], dp[c - wgt[i - 1]] + val[i - 1]); } } } return dp[cap]; } ``` === "C#" ```csharp title="unbounded_knapsack.cs" /* 完全背包:空间优化后的动态规划 */ int UnboundedKnapsackDPComp(int[] wgt, int[] val, int cap) { int n = wgt.Length; // 初始化 dp 表 int[] dp = new int[cap + 1]; // 状态转移 for (int i = 1; i <= n; i++) { for (int c = 1; c <= cap; c++) { if (wgt[i - 1] > c) { // 若超过背包容量,则不选物品 i dp[c] = dp[c]; } else { // 不选和选物品 i 这两种方案的较大值 dp[c] = Math.Max(dp[c], dp[c - wgt[i - 1]] + val[i - 1]); } } } return dp[cap]; } ``` === "Go" ```go title="unbounded_knapsack.go" /* 完全背包:空间优化后的动态规划 */ func unboundedKnapsackDPComp(wgt, val []int, cap int) int { n := len(wgt) // 初始化 dp 表 dp := make([]int, cap+1) // 状态转移 for i := 1; i <= n; i++ { for c := 1; c <= cap; c++ { if wgt[i-1] > c { // 若超过背包容量,则不选物品 i dp[c] = dp[c] } else { // 不选和选物品 i 这两种方案的较大值 dp[c] = int(math.Max(float64(dp[c]), float64(dp[c-wgt[i-1]]+val[i-1]))) } } } return dp[cap] } ``` === "Swift" ```swift title="unbounded_knapsack.swift" /* 完全背包:空间优化后的动态规划 */ func unboundedKnapsackDPComp(wgt: [Int], val: [Int], cap: Int) -> Int { let n = wgt.count // 初始化 dp 表 var dp = Array(repeating: 0, count: cap + 1) // 状态转移 for i in 1 ... n { for c in 1 ... cap { if wgt[i - 1] > c { // 若超过背包容量,则不选物品 i dp[c] = dp[c] } else { // 不选和选物品 i 这两种方案的较大值 dp[c] = max(dp[c], dp[c - wgt[i - 1]] + val[i - 1]) } } } return dp[cap] } ``` === "JS" ```javascript title="unbounded_knapsack.js" /* 完全背包:状态压缩后的动态规划 */ function unboundedKnapsackDPComp(wgt, val, cap) { const n = wgt.length; // 初始化 dp 表 const dp = Array.from({ length: cap + 1 }, () => 0); // 状态转移 for (let i = 1; i <= n; i++) { for (let c = 1; c <= cap; c++) { if (wgt[i - 1] > c) { // 若超过背包容量,则不选物品 i dp[c] = dp[c]; } else { // 不选和选物品 i 这两种方案的较大值 dp[c] = Math.max(dp[c], dp[c - wgt[i - 1]] + val[i - 1]); } } } return dp[cap]; } ``` === "TS" ```typescript title="unbounded_knapsack.ts" /* 完全背包:状态压缩后的动态规划 */ function unboundedKnapsackDPComp( wgt: Array, val: Array, cap: number ): number { const n = wgt.length; // 初始化 dp 表 const dp = Array.from({ length: cap + 1 }, () => 0); // 状态转移 for (let i = 1; i <= n; i++) { for (let c = 1; c <= cap; c++) { if (wgt[i - 1] > c) { // 若超过背包容量,则不选物品 i dp[c] = dp[c]; } else { // 不选和选物品 i 这两种方案的较大值 dp[c] = Math.max(dp[c], dp[c - wgt[i - 1]] + val[i - 1]); } } } return dp[cap]; } ``` === "Dart" ```dart title="unbounded_knapsack.dart" /* 完全背包:空间优化后的动态规划 */ int unboundedKnapsackDPComp(List wgt, List val, int cap) { int n = wgt.length; // 初始化 dp 表 List dp = List.filled(cap + 1, 0); // 状态转移 for (int i = 1; i <= n; i++) { for (int c = 1; c <= cap; c++) { if (wgt[i - 1] > c) { // 若超过背包容量,则不选物品 i dp[c] = dp[c]; } else { // 不选和选物品 i 这两种方案的较大值 dp[c] = max(dp[c], dp[c - wgt[i - 1]] + val[i - 1]); } } } return dp[cap]; } ``` === "Rust" ```rust title="unbounded_knapsack.rs" /* 完全背包:空间优化后的动态规划 */ fn unbounded_knapsack_dp_comp(wgt: &[i32], val: &[i32], cap: usize) -> i32 { let n = wgt.len(); // 初始化 dp 表 let mut dp = vec![0; cap + 1]; // 状态转移 for i in 1..=n { for c in 1..=cap { if wgt[i - 1] > c as i32 { // 若超过背包容量,则不选物品 i dp[c] = dp[c]; } else { // 不选和选物品 i 这两种方案的较大值 dp[c] = std::cmp::max(dp[c], dp[c - wgt[i - 1] as usize] + val[i - 1]); } } } dp[cap] } ``` === "C" ```c title="unbounded_knapsack.c" /* 完全背包:空间优化后的动态规划 */ int unboundedKnapsackDPComp(int wgt[], int val[], int cap, int wgtSize) { int n = wgtSize; // 初始化 dp 表 int *dp = calloc(cap + 1, sizeof(int)); // 状态转移 for (int i = 1; i <= n; i++) { for (int c = 1; c <= cap; c++) { if (wgt[i - 1] > c) { // 若超过背包容量,则不选物品 i dp[c] = dp[c]; } else { // 不选和选物品 i 这两种方案的较大值 dp[c] = myMax(dp[c], dp[c - wgt[i - 1]] + val[i - 1]); } } } int res = dp[cap]; // 释放内存 free(dp); return res; } ``` === "Kotlin" ```kotlin title="unbounded_knapsack.kt" /* 完全背包:空间优化后的动态规划 */ fun unboundedKnapsackDPComp( wgt: IntArray, _val: IntArray, cap: Int ): Int { val n = wgt.size // 初始化 dp 表 val dp = IntArray(cap + 1) // 状态转移 for (i in 1..n) { for (c in 1..cap) { if (wgt[i - 1] > c) { // 若超过背包容量,则不选物品 i dp[c] = dp[c] } else { // 不选和选物品 i 这两种方案的较大值 dp[c] = max(dp[c], dp[c - wgt[i - 1]] + _val[i - 1]) } } } return dp[cap] } ``` === "Ruby" ```ruby title="unbounded_knapsack.rb" [class]{}-[func]{unbounded_knapsack_dp_comp} ``` === "Zig" ```zig title="unbounded_knapsack.zig" // 完全背包:空间优化后的动态规划 fn unboundedKnapsackDPComp(comptime wgt: []i32, val: []i32, comptime cap: usize) i32 { comptime var n = wgt.len; // 初始化 dp 表 var dp = [_]i32{0} ** (cap + 1); // 状态转移 for (1..n + 1) |i| { for (1..cap + 1) |c| { if (wgt[i - 1] > c) { // 若超过背包容量,则不选物品 i dp[c] = dp[c]; } else { // 不选和选物品 i 这两种方案的较大值 dp[c] = @max(dp[c], dp[c - @as(usize, @intCast(wgt[i - 1]))] + val[i - 1]); } } } return dp[cap]; } ``` ??? pythontutor "Code Visualization"
## 14.5.2   Coin change problem The knapsack problem is a representative of a large class of dynamic programming problems and has many variants, such as the coin change problem. !!! question Given $n$ types of coins, the denomination of the $i^{th}$ type of coin is $coins[i - 1]$, and the target amount is $amt$. **Each type of coin can be selected multiple times**. What is the minimum number of coins needed to make up the target amount? If it is impossible to make up the target amount, return $-1$. See the example below. ![Example data for the coin change problem](unbounded_knapsack_problem.assets/coin_change_example.png){ class="animation-figure" }

Figure 14-24   Example data for the coin change problem

### 1.   Dynamic programming approach **The coin change can be seen as a special case of the unbounded knapsack problem**, sharing the following similarities and differences. - The two problems can be converted into each other: "item" corresponds to "coin", "item weight" corresponds to "coin denomination", and "backpack capacity" corresponds to "target amount". - The optimization goals are opposite: the unbounded knapsack problem aims to maximize the value of items, while the coin change problem aims to minimize the number of coins. - The unbounded knapsack problem seeks solutions "not exceeding" the backpack capacity, while the coin change seeks solutions that "exactly" make up the target amount. **First step: Think through each round's decision-making, define the state, and thus derive the $dp$ table** The state $[i, a]$ corresponds to the sub-problem: **the minimum number of coins that can make up the amount $a$ using the first $i$ types of coins**, denoted as $dp[i, a]$. The two-dimensional $dp$ table is of size $(n+1) \times (amt+1)$. **Second step: Identify the optimal substructure and derive the state transition equation** This problem differs from the unbounded knapsack problem in two aspects of the state transition equation. - This problem seeks the minimum, so the operator $\max()$ needs to be changed to $\min()$. - The optimization is focused on the number of coins, so simply add $+1$ when a coin is chosen. $$ dp[i, a] = \min(dp[i-1, a], dp[i, a - coins[i-1]] + 1) $$ **Third step: Define boundary conditions and state transition order** When the target amount is $0$, the minimum number of coins needed to make it up is $0$, so all $dp[i, 0]$ in the first column are $0$. When there are no coins, **it is impossible to make up any amount >0**, which is an invalid solution. To allow the $\min()$ function in the state transition equation to recognize and filter out invalid solutions, consider using $+\infty$ to represent them, i.e., set all $dp[0, a]$ in the first row to $+\infty$. ### 2.   Code implementation Most programming languages do not provide a $+\infty$ variable, only the maximum value of an integer `int` can be used as a substitute. This can lead to overflow: the $+1$ operation in the state transition equation may overflow. For this reason, we use the number $amt + 1$ to represent an invalid solution, because the maximum number of coins needed to make up $amt$ is at most $amt$. Before returning the result, check if $dp[n, amt]$ equals $amt + 1$, and if so, return $-1$, indicating that the target amount cannot be made up. The code is as follows: === "Python" ```python title="coin_change.py" def coin_change_dp(coins: list[int], amt: int) -> int: """零钱兑换:动态规划""" n = len(coins) MAX = amt + 1 # 初始化 dp 表 dp = [[0] * (amt + 1) for _ in range(n + 1)] # 状态转移:首行首列 for a in range(1, amt + 1): dp[0][a] = MAX # 状态转移:其余行和列 for i in range(1, n + 1): for a in range(1, amt + 1): if coins[i - 1] > a: # 若超过目标金额,则不选硬币 i dp[i][a] = dp[i - 1][a] else: # 不选和选硬币 i 这两种方案的较小值 dp[i][a] = min(dp[i - 1][a], dp[i][a - coins[i - 1]] + 1) return dp[n][amt] if dp[n][amt] != MAX else -1 ``` === "C++" ```cpp title="coin_change.cpp" /* 零钱兑换:动态规划 */ int coinChangeDP(vector &coins, int amt) { int n = coins.size(); int MAX = amt + 1; // 初始化 dp 表 vector> dp(n + 1, vector(amt + 1, 0)); // 状态转移:首行首列 for (int a = 1; a <= amt; a++) { dp[0][a] = MAX; } // 状态转移:其余行和列 for (int i = 1; i <= n; i++) { for (int a = 1; a <= amt; a++) { if (coins[i - 1] > a) { // 若超过目标金额,则不选硬币 i dp[i][a] = dp[i - 1][a]; } else { // 不选和选硬币 i 这两种方案的较小值 dp[i][a] = min(dp[i - 1][a], dp[i][a - coins[i - 1]] + 1); } } } return dp[n][amt] != MAX ? dp[n][amt] : -1; } ``` === "Java" ```java title="coin_change.java" /* 零钱兑换:动态规划 */ int coinChangeDP(int[] coins, int amt) { int n = coins.length; int MAX = amt + 1; // 初始化 dp 表 int[][] dp = new int[n + 1][amt + 1]; // 状态转移:首行首列 for (int a = 1; a <= amt; a++) { dp[0][a] = MAX; } // 状态转移:其余行和列 for (int i = 1; i <= n; i++) { for (int a = 1; a <= amt; a++) { if (coins[i - 1] > a) { // 若超过目标金额,则不选硬币 i dp[i][a] = dp[i - 1][a]; } else { // 不选和选硬币 i 这两种方案的较小值 dp[i][a] = Math.min(dp[i - 1][a], dp[i][a - coins[i - 1]] + 1); } } } return dp[n][amt] != MAX ? dp[n][amt] : -1; } ``` === "C#" ```csharp title="coin_change.cs" /* 零钱兑换:动态规划 */ int CoinChangeDP(int[] coins, int amt) { int n = coins.Length; int MAX = amt + 1; // 初始化 dp 表 int[,] dp = new int[n + 1, amt + 1]; // 状态转移:首行首列 for (int a = 1; a <= amt; a++) { dp[0, a] = MAX; } // 状态转移:其余行和列 for (int i = 1; i <= n; i++) { for (int a = 1; a <= amt; a++) { if (coins[i - 1] > a) { // 若超过目标金额,则不选硬币 i dp[i, a] = dp[i - 1, a]; } else { // 不选和选硬币 i 这两种方案的较小值 dp[i, a] = Math.Min(dp[i - 1, a], dp[i, a - coins[i - 1]] + 1); } } } return dp[n, amt] != MAX ? dp[n, amt] : -1; } ``` === "Go" ```go title="coin_change.go" /* 零钱兑换:动态规划 */ func coinChangeDP(coins []int, amt int) int { n := len(coins) max := amt + 1 // 初始化 dp 表 dp := make([][]int, n+1) for i := 0; i <= n; i++ { dp[i] = make([]int, amt+1) } // 状态转移:首行首列 for a := 1; a <= amt; a++ { dp[0][a] = max } // 状态转移:其余行和列 for i := 1; i <= n; i++ { for a := 1; a <= amt; a++ { if coins[i-1] > a { // 若超过目标金额,则不选硬币 i dp[i][a] = dp[i-1][a] } else { // 不选和选硬币 i 这两种方案的较小值 dp[i][a] = int(math.Min(float64(dp[i-1][a]), float64(dp[i][a-coins[i-1]]+1))) } } } if dp[n][amt] != max { return dp[n][amt] } return -1 } ``` === "Swift" ```swift title="coin_change.swift" /* 零钱兑换:动态规划 */ func coinChangeDP(coins: [Int], amt: Int) -> Int { let n = coins.count let MAX = amt + 1 // 初始化 dp 表 var dp = Array(repeating: Array(repeating: 0, count: amt + 1), count: n + 1) // 状态转移:首行首列 for a in 1 ... amt { dp[0][a] = MAX } // 状态转移:其余行和列 for i in 1 ... n { for a in 1 ... amt { if coins[i - 1] > a { // 若超过目标金额,则不选硬币 i dp[i][a] = dp[i - 1][a] } else { // 不选和选硬币 i 这两种方案的较小值 dp[i][a] = min(dp[i - 1][a], dp[i][a - coins[i - 1]] + 1) } } } return dp[n][amt] != MAX ? dp[n][amt] : -1 } ``` === "JS" ```javascript title="coin_change.js" /* 零钱兑换:动态规划 */ function coinChangeDP(coins, amt) { const n = coins.length; const MAX = amt + 1; // 初始化 dp 表 const dp = Array.from({ length: n + 1 }, () => Array.from({ length: amt + 1 }, () => 0) ); // 状态转移:首行首列 for (let a = 1; a <= amt; a++) { dp[0][a] = MAX; } // 状态转移:其余行和列 for (let i = 1; i <= n; i++) { for (let a = 1; a <= amt; a++) { if (coins[i - 1] > a) { // 若超过目标金额,则不选硬币 i dp[i][a] = dp[i - 1][a]; } else { // 不选和选硬币 i 这两种方案的较小值 dp[i][a] = Math.min(dp[i - 1][a], dp[i][a - coins[i - 1]] + 1); } } } return dp[n][amt] !== MAX ? dp[n][amt] : -1; } ``` === "TS" ```typescript title="coin_change.ts" /* 零钱兑换:动态规划 */ function coinChangeDP(coins: Array, amt: number): number { const n = coins.length; const MAX = amt + 1; // 初始化 dp 表 const dp = Array.from({ length: n + 1 }, () => Array.from({ length: amt + 1 }, () => 0) ); // 状态转移:首行首列 for (let a = 1; a <= amt; a++) { dp[0][a] = MAX; } // 状态转移:其余行和列 for (let i = 1; i <= n; i++) { for (let a = 1; a <= amt; a++) { if (coins[i - 1] > a) { // 若超过目标金额,则不选硬币 i dp[i][a] = dp[i - 1][a]; } else { // 不选和选硬币 i 这两种方案的较小值 dp[i][a] = Math.min(dp[i - 1][a], dp[i][a - coins[i - 1]] + 1); } } } return dp[n][amt] !== MAX ? dp[n][amt] : -1; } ``` === "Dart" ```dart title="coin_change.dart" /* 零钱兑换:动态规划 */ int coinChangeDP(List coins, int amt) { int n = coins.length; int MAX = amt + 1; // 初始化 dp 表 List> dp = List.generate(n + 1, (index) => List.filled(amt + 1, 0)); // 状态转移:首行首列 for (int a = 1; a <= amt; a++) { dp[0][a] = MAX; } // 状态转移:其余行和列 for (int i = 1; i <= n; i++) { for (int a = 1; a <= amt; a++) { if (coins[i - 1] > a) { // 若超过目标金额,则不选硬币 i dp[i][a] = dp[i - 1][a]; } else { // 不选和选硬币 i 这两种方案的较小值 dp[i][a] = min(dp[i - 1][a], dp[i][a - coins[i - 1]] + 1); } } } return dp[n][amt] != MAX ? dp[n][amt] : -1; } ``` === "Rust" ```rust title="coin_change.rs" /* 零钱兑换:动态规划 */ fn coin_change_dp(coins: &[i32], amt: usize) -> i32 { let n = coins.len(); let max = amt + 1; // 初始化 dp 表 let mut dp = vec![vec![0; amt + 1]; n + 1]; // 状态转移:首行首列 for a in 1..=amt { dp[0][a] = max; } // 状态转移:其余行和列 for i in 1..=n { for a in 1..=amt { if coins[i - 1] > a as i32 { // 若超过目标金额,则不选硬币 i dp[i][a] = dp[i - 1][a]; } else { // 不选和选硬币 i 这两种方案的较小值 dp[i][a] = std::cmp::min(dp[i - 1][a], dp[i][a - coins[i - 1] as usize] + 1); } } } if dp[n][amt] != max { return dp[n][amt] as i32; } else { -1 } } ``` === "C" ```c title="coin_change.c" /* 零钱兑换:动态规划 */ int coinChangeDP(int coins[], int amt, int coinsSize) { int n = coinsSize; int MAX = amt + 1; // 初始化 dp 表 int **dp = malloc((n + 1) * sizeof(int *)); for (int i = 0; i <= n; i++) { dp[i] = calloc(amt + 1, sizeof(int)); } // 状态转移:首行首列 for (int a = 1; a <= amt; a++) { dp[0][a] = MAX; } // 状态转移:其余行和列 for (int i = 1; i <= n; i++) { for (int a = 1; a <= amt; a++) { if (coins[i - 1] > a) { // 若超过目标金额,则不选硬币 i dp[i][a] = dp[i - 1][a]; } else { // 不选和选硬币 i 这两种方案的较小值 dp[i][a] = myMin(dp[i - 1][a], dp[i][a - coins[i - 1]] + 1); } } } int res = dp[n][amt] != MAX ? dp[n][amt] : -1; // 释放内存 for (int i = 0; i <= n; i++) { free(dp[i]); } free(dp); return res; } ``` === "Kotlin" ```kotlin title="coin_change.kt" /* 零钱兑换:动态规划 */ fun coinChangeDP(coins: IntArray, amt: Int): Int { val n = coins.size val MAX = amt + 1 // 初始化 dp 表 val dp = Array(n + 1) { IntArray(amt + 1) } // 状态转移:首行首列 for (a in 1..amt) { dp[0][a] = MAX } // 状态转移:其余行和列 for (i in 1..n) { for (a in 1..amt) { if (coins[i - 1] > a) { // 若超过目标金额,则不选硬币 i dp[i][a] = dp[i - 1][a] } else { // 不选和选硬币 i 这两种方案的较小值 dp[i][a] = min(dp[i - 1][a], dp[i][a - coins[i - 1]] + 1) } } } return if (dp[n][amt] != MAX) dp[n][amt] else -1 } ``` === "Ruby" ```ruby title="coin_change.rb" [class]{}-[func]{coin_change_dp} ``` === "Zig" ```zig title="coin_change.zig" // 零钱兑换:动态规划 fn coinChangeDP(comptime coins: []i32, comptime amt: usize) i32 { comptime var n = coins.len; comptime var max = amt + 1; // 初始化 dp 表 var dp = [_][amt + 1]i32{[_]i32{0} ** (amt + 1)} ** (n + 1); // 状态转移:首行首列 for (1..amt + 1) |a| { dp[0][a] = max; } // 状态转移:其余行和列 for (1..n + 1) |i| { for (1..amt + 1) |a| { if (coins[i - 1] > @as(i32, @intCast(a))) { // 若超过目标金额,则不选硬币 i dp[i][a] = dp[i - 1][a]; } else { // 不选和选硬币 i 这两种方案的较小值 dp[i][a] = @min(dp[i - 1][a], dp[i][a - @as(usize, @intCast(coins[i - 1]))] + 1); } } } if (dp[n][amt] != max) { return @intCast(dp[n][amt]); } else { return -1; } } ``` ??? pythontutor "Code Visualization"
Figure 14-25 show the dynamic programming process for the coin change problem, which is very similar to the unbounded knapsack problem. === "<1>" ![Dynamic programming process for the coin change problem](unbounded_knapsack_problem.assets/coin_change_dp_step1.png){ class="animation-figure" } === "<2>" ![coin_change_dp_step2](unbounded_knapsack_problem.assets/coin_change_dp_step2.png){ class="animation-figure" } === "<3>" ![coin_change_dp_step3](unbounded_knapsack_problem.assets/coin_change_dp_step3.png){ class="animation-figure" } === "<4>" ![coin_change_dp_step4](unbounded_knapsack_problem.assets/coin_change_dp_step4.png){ class="animation-figure" } === "<5>" ![coin_change_dp_step5](unbounded_knapsack_problem.assets/coin_change_dp_step5.png){ class="animation-figure" } === "<6>" ![coin_change_dp_step6](unbounded_knapsack_problem.assets/coin_change_dp_step6.png){ class="animation-figure" } === "<7>" ![coin_change_dp_step7](unbounded_knapsack_problem.assets/coin_change_dp_step7.png){ class="animation-figure" } === "<8>" ![coin_change_dp_step8](unbounded_knapsack_problem.assets/coin_change_dp_step8.png){ class="animation-figure" } === "<9>" ![coin_change_dp_step9](unbounded_knapsack_problem.assets/coin_change_dp_step9.png){ class="animation-figure" } === "<10>" ![coin_change_dp_step10](unbounded_knapsack_problem.assets/coin_change_dp_step10.png){ class="animation-figure" } === "<11>" ![coin_change_dp_step11](unbounded_knapsack_problem.assets/coin_change_dp_step11.png){ class="animation-figure" } === "<12>" ![coin_change_dp_step12](unbounded_knapsack_problem.assets/coin_change_dp_step12.png){ class="animation-figure" } === "<13>" ![coin_change_dp_step13](unbounded_knapsack_problem.assets/coin_change_dp_step13.png){ class="animation-figure" } === "<14>" ![coin_change_dp_step14](unbounded_knapsack_problem.assets/coin_change_dp_step14.png){ class="animation-figure" } === "<15>" ![coin_change_dp_step15](unbounded_knapsack_problem.assets/coin_change_dp_step15.png){ class="animation-figure" }

Figure 14-25   Dynamic programming process for the coin change problem

### 3.   Space optimization The space optimization for the coin change problem is handled in the same way as for the unbounded knapsack problem: === "Python" ```python title="coin_change.py" def coin_change_dp_comp(coins: list[int], amt: int) -> int: """零钱兑换:空间优化后的动态规划""" n = len(coins) MAX = amt + 1 # 初始化 dp 表 dp = [MAX] * (amt + 1) dp[0] = 0 # 状态转移 for i in range(1, n + 1): # 正序遍历 for a in range(1, amt + 1): if coins[i - 1] > a: # 若超过目标金额,则不选硬币 i dp[a] = dp[a] else: # 不选和选硬币 i 这两种方案的较小值 dp[a] = min(dp[a], dp[a - coins[i - 1]] + 1) return dp[amt] if dp[amt] != MAX else -1 ``` === "C++" ```cpp title="coin_change.cpp" /* 零钱兑换:空间优化后的动态规划 */ int coinChangeDPComp(vector &coins, int amt) { int n = coins.size(); int MAX = amt + 1; // 初始化 dp 表 vector dp(amt + 1, MAX); dp[0] = 0; // 状态转移 for (int i = 1; i <= n; i++) { for (int a = 1; a <= amt; a++) { if (coins[i - 1] > a) { // 若超过目标金额,则不选硬币 i dp[a] = dp[a]; } else { // 不选和选硬币 i 这两种方案的较小值 dp[a] = min(dp[a], dp[a - coins[i - 1]] + 1); } } } return dp[amt] != MAX ? dp[amt] : -1; } ``` === "Java" ```java title="coin_change.java" /* 零钱兑换:空间优化后的动态规划 */ int coinChangeDPComp(int[] coins, int amt) { int n = coins.length; int MAX = amt + 1; // 初始化 dp 表 int[] dp = new int[amt + 1]; Arrays.fill(dp, MAX); dp[0] = 0; // 状态转移 for (int i = 1; i <= n; i++) { for (int a = 1; a <= amt; a++) { if (coins[i - 1] > a) { // 若超过目标金额,则不选硬币 i dp[a] = dp[a]; } else { // 不选和选硬币 i 这两种方案的较小值 dp[a] = Math.min(dp[a], dp[a - coins[i - 1]] + 1); } } } return dp[amt] != MAX ? dp[amt] : -1; } ``` === "C#" ```csharp title="coin_change.cs" /* 零钱兑换:空间优化后的动态规划 */ int CoinChangeDPComp(int[] coins, int amt) { int n = coins.Length; int MAX = amt + 1; // 初始化 dp 表 int[] dp = new int[amt + 1]; Array.Fill(dp, MAX); dp[0] = 0; // 状态转移 for (int i = 1; i <= n; i++) { for (int a = 1; a <= amt; a++) { if (coins[i - 1] > a) { // 若超过目标金额,则不选硬币 i dp[a] = dp[a]; } else { // 不选和选硬币 i 这两种方案的较小值 dp[a] = Math.Min(dp[a], dp[a - coins[i - 1]] + 1); } } } return dp[amt] != MAX ? dp[amt] : -1; } ``` === "Go" ```go title="coin_change.go" /* 零钱兑换:动态规划 */ func coinChangeDPComp(coins []int, amt int) int { n := len(coins) max := amt + 1 // 初始化 dp 表 dp := make([]int, amt+1) for i := 1; i <= amt; i++ { dp[i] = max } // 状态转移 for i := 1; i <= n; i++ { // 正序遍历 for a := 1; a <= amt; a++ { if coins[i-1] > a { // 若超过目标金额,则不选硬币 i dp[a] = dp[a] } else { // 不选和选硬币 i 这两种方案的较小值 dp[a] = int(math.Min(float64(dp[a]), float64(dp[a-coins[i-1]]+1))) } } } if dp[amt] != max { return dp[amt] } return -1 } ``` === "Swift" ```swift title="coin_change.swift" /* 零钱兑换:空间优化后的动态规划 */ func coinChangeDPComp(coins: [Int], amt: Int) -> Int { let n = coins.count let MAX = amt + 1 // 初始化 dp 表 var dp = Array(repeating: MAX, count: amt + 1) dp[0] = 0 // 状态转移 for i in 1 ... n { for a in 1 ... amt { if coins[i - 1] > a { // 若超过目标金额,则不选硬币 i dp[a] = dp[a] } else { // 不选和选硬币 i 这两种方案的较小值 dp[a] = min(dp[a], dp[a - coins[i - 1]] + 1) } } } return dp[amt] != MAX ? dp[amt] : -1 } ``` === "JS" ```javascript title="coin_change.js" /* 零钱兑换:状态压缩后的动态规划 */ function coinChangeDPComp(coins, amt) { const n = coins.length; const MAX = amt + 1; // 初始化 dp 表 const dp = Array.from({ length: amt + 1 }, () => MAX); dp[0] = 0; // 状态转移 for (let i = 1; i <= n; i++) { for (let a = 1; a <= amt; a++) { if (coins[i - 1] > a) { // 若超过目标金额,则不选硬币 i dp[a] = dp[a]; } else { // 不选和选硬币 i 这两种方案的较小值 dp[a] = Math.min(dp[a], dp[a - coins[i - 1]] + 1); } } } return dp[amt] !== MAX ? dp[amt] : -1; } ``` === "TS" ```typescript title="coin_change.ts" /* 零钱兑换:状态压缩后的动态规划 */ function coinChangeDPComp(coins: Array, amt: number): number { const n = coins.length; const MAX = amt + 1; // 初始化 dp 表 const dp = Array.from({ length: amt + 1 }, () => MAX); dp[0] = 0; // 状态转移 for (let i = 1; i <= n; i++) { for (let a = 1; a <= amt; a++) { if (coins[i - 1] > a) { // 若超过目标金额,则不选硬币 i dp[a] = dp[a]; } else { // 不选和选硬币 i 这两种方案的较小值 dp[a] = Math.min(dp[a], dp[a - coins[i - 1]] + 1); } } } return dp[amt] !== MAX ? dp[amt] : -1; } ``` === "Dart" ```dart title="coin_change.dart" /* 零钱兑换:空间优化后的动态规划 */ int coinChangeDPComp(List coins, int amt) { int n = coins.length; int MAX = amt + 1; // 初始化 dp 表 List dp = List.filled(amt + 1, MAX); dp[0] = 0; // 状态转移 for (int i = 1; i <= n; i++) { for (int a = 1; a <= amt; a++) { if (coins[i - 1] > a) { // 若超过目标金额,则不选硬币 i dp[a] = dp[a]; } else { // 不选和选硬币 i 这两种方案的较小值 dp[a] = min(dp[a], dp[a - coins[i - 1]] + 1); } } } return dp[amt] != MAX ? dp[amt] : -1; } ``` === "Rust" ```rust title="coin_change.rs" /* 零钱兑换:空间优化后的动态规划 */ fn coin_change_dp_comp(coins: &[i32], amt: usize) -> i32 { let n = coins.len(); let max = amt + 1; // 初始化 dp 表 let mut dp = vec![0; amt + 1]; dp.fill(max); dp[0] = 0; // 状态转移 for i in 1..=n { for a in 1..=amt { if coins[i - 1] > a as i32 { // 若超过目标金额,则不选硬币 i dp[a] = dp[a]; } else { // 不选和选硬币 i 这两种方案的较小值 dp[a] = std::cmp::min(dp[a], dp[a - coins[i - 1] as usize] + 1); } } } if dp[amt] != max { return dp[amt] as i32; } else { -1 } } ``` === "C" ```c title="coin_change.c" /* 零钱兑换:空间优化后的动态规划 */ int coinChangeDPComp(int coins[], int amt, int coinsSize) { int n = coinsSize; int MAX = amt + 1; // 初始化 dp 表 int *dp = malloc((amt + 1) * sizeof(int)); for (int j = 1; j <= amt; j++) { dp[j] = MAX; } dp[0] = 0; // 状态转移 for (int i = 1; i <= n; i++) { for (int a = 1; a <= amt; a++) { if (coins[i - 1] > a) { // 若超过目标金额,则不选硬币 i dp[a] = dp[a]; } else { // 不选和选硬币 i 这两种方案的较小值 dp[a] = myMin(dp[a], dp[a - coins[i - 1]] + 1); } } } int res = dp[amt] != MAX ? dp[amt] : -1; // 释放内存 free(dp); return res; } ``` === "Kotlin" ```kotlin title="coin_change.kt" /* 零钱兑换:空间优化后的动态规划 */ fun coinChangeDPComp(coins: IntArray, amt: Int): Int { val n = coins.size val MAX = amt + 1 // 初始化 dp 表 val dp = IntArray(amt + 1) dp.fill(MAX) dp[0] = 0 // 状态转移 for (i in 1..n) { for (a in 1..amt) { if (coins[i - 1] > a) { // 若超过目标金额,则不选硬币 i dp[a] = dp[a] } else { // 不选和选硬币 i 这两种方案的较小值 dp[a] = min(dp[a], dp[a - coins[i - 1]] + 1) } } } return if (dp[amt] != MAX) dp[amt] else -1 } ``` === "Ruby" ```ruby title="coin_change.rb" [class]{}-[func]{coin_change_dp_comp} ``` === "Zig" ```zig title="coin_change.zig" // 零钱兑换:空间优化后的动态规划 fn coinChangeDPComp(comptime coins: []i32, comptime amt: usize) i32 { comptime var n = coins.len; comptime var max = amt + 1; // 初始化 dp 表 var dp = [_]i32{0} ** (amt + 1); @memset(&dp, max); dp[0] = 0; // 状态转移 for (1..n + 1) |i| { for (1..amt + 1) |a| { if (coins[i - 1] > @as(i32, @intCast(a))) { // 若超过目标金额,则不选硬币 i dp[a] = dp[a]; } else { // 不选和选硬币 i 这两种方案的较小值 dp[a] = @min(dp[a], dp[a - @as(usize, @intCast(coins[i - 1]))] + 1); } } } if (dp[amt] != max) { return @intCast(dp[amt]); } else { return -1; } } ``` ??? pythontutor "Code Visualization"
## 14.5.3   Coin change problem II !!! question Given $n$ types of coins, where the denomination of the $i^{th}$ type of coin is $coins[i - 1]$, and the target amount is $amt$. Each type of coin can be selected multiple times, **ask how many combinations of coins can make up the target amount**. See the example below. ![Example data for Coin Change Problem II](unbounded_knapsack_problem.assets/coin_change_ii_example.png){ class="animation-figure" }

Figure 14-26   Example data for Coin Change Problem II

### 1.   Dynamic programming approach Compared to the previous problem, the goal of this problem is to determine the number of combinations, so the sub-problem becomes: **the number of combinations that can make up amount $a$ using the first $i$ types of coins**. The $dp$ table remains a two-dimensional matrix of size $(n+1) \times (amt + 1)$. The number of combinations for the current state is the sum of the combinations from not selecting the current coin and selecting the current coin. The state transition equation is: $$ dp[i, a] = dp[i-1, a] + dp[i, a - coins[i-1]] $$ When the target amount is $0$, no coins are needed to make up the target amount, so all $dp[i, 0]$ in the first column should be initialized to $1$. When there are no coins, it is impossible to make up any amount >0, so all $dp[0, a]$ in the first row should be set to $0$. ### 2.   Code implementation === "Python" ```python title="coin_change_ii.py" def coin_change_ii_dp(coins: list[int], amt: int) -> int: """零钱兑换 II:动态规划""" n = len(coins) # 初始化 dp 表 dp = [[0] * (amt + 1) for _ in range(n + 1)] # 初始化首列 for i in range(n + 1): dp[i][0] = 1 # 状态转移 for i in range(1, n + 1): for a in range(1, amt + 1): if coins[i - 1] > a: # 若超过目标金额,则不选硬币 i dp[i][a] = dp[i - 1][a] else: # 不选和选硬币 i 这两种方案之和 dp[i][a] = dp[i - 1][a] + dp[i][a - coins[i - 1]] return dp[n][amt] ``` === "C++" ```cpp title="coin_change_ii.cpp" /* 零钱兑换 II:动态规划 */ int coinChangeIIDP(vector &coins, int amt) { int n = coins.size(); // 初始化 dp 表 vector> dp(n + 1, vector(amt + 1, 0)); // 初始化首列 for (int i = 0; i <= n; i++) { dp[i][0] = 1; } // 状态转移 for (int i = 1; i <= n; i++) { for (int a = 1; a <= amt; a++) { if (coins[i - 1] > a) { // 若超过目标金额,则不选硬币 i dp[i][a] = dp[i - 1][a]; } else { // 不选和选硬币 i 这两种方案之和 dp[i][a] = dp[i - 1][a] + dp[i][a - coins[i - 1]]; } } } return dp[n][amt]; } ``` === "Java" ```java title="coin_change_ii.java" /* 零钱兑换 II:动态规划 */ int coinChangeIIDP(int[] coins, int amt) { int n = coins.length; // 初始化 dp 表 int[][] dp = new int[n + 1][amt + 1]; // 初始化首列 for (int i = 0; i <= n; i++) { dp[i][0] = 1; } // 状态转移 for (int i = 1; i <= n; i++) { for (int a = 1; a <= amt; a++) { if (coins[i - 1] > a) { // 若超过目标金额,则不选硬币 i dp[i][a] = dp[i - 1][a]; } else { // 不选和选硬币 i 这两种方案之和 dp[i][a] = dp[i - 1][a] + dp[i][a - coins[i - 1]]; } } } return dp[n][amt]; } ``` === "C#" ```csharp title="coin_change_ii.cs" /* 零钱兑换 II:动态规划 */ int CoinChangeIIDP(int[] coins, int amt) { int n = coins.Length; // 初始化 dp 表 int[,] dp = new int[n + 1, amt + 1]; // 初始化首列 for (int i = 0; i <= n; i++) { dp[i, 0] = 1; } // 状态转移 for (int i = 1; i <= n; i++) { for (int a = 1; a <= amt; a++) { if (coins[i - 1] > a) { // 若超过目标金额,则不选硬币 i dp[i, a] = dp[i - 1, a]; } else { // 不选和选硬币 i 这两种方案之和 dp[i, a] = dp[i - 1, a] + dp[i, a - coins[i - 1]]; } } } return dp[n, amt]; } ``` === "Go" ```go title="coin_change_ii.go" /* 零钱兑换 II:动态规划 */ func coinChangeIIDP(coins []int, amt int) int { n := len(coins) // 初始化 dp 表 dp := make([][]int, n+1) for i := 0; i <= n; i++ { dp[i] = make([]int, amt+1) } // 初始化首列 for i := 0; i <= n; i++ { dp[i][0] = 1 } // 状态转移:其余行和列 for i := 1; i <= n; i++ { for a := 1; a <= amt; a++ { if coins[i-1] > a { // 若超过目标金额,则不选硬币 i dp[i][a] = dp[i-1][a] } else { // 不选和选硬币 i 这两种方案之和 dp[i][a] = dp[i-1][a] + dp[i][a-coins[i-1]] } } } return dp[n][amt] } ``` === "Swift" ```swift title="coin_change_ii.swift" /* 零钱兑换 II:动态规划 */ func coinChangeIIDP(coins: [Int], amt: Int) -> Int { let n = coins.count // 初始化 dp 表 var dp = Array(repeating: Array(repeating: 0, count: amt + 1), count: n + 1) // 初始化首列 for i in 0 ... n { dp[i][0] = 1 } // 状态转移 for i in 1 ... n { for a in 1 ... amt { if coins[i - 1] > a { // 若超过目标金额,则不选硬币 i dp[i][a] = dp[i - 1][a] } else { // 不选和选硬币 i 这两种方案之和 dp[i][a] = dp[i - 1][a] + dp[i][a - coins[i - 1]] } } } return dp[n][amt] } ``` === "JS" ```javascript title="coin_change_ii.js" /* 零钱兑换 II:动态规划 */ function coinChangeIIDP(coins, amt) { const n = coins.length; // 初始化 dp 表 const dp = Array.from({ length: n + 1 }, () => Array.from({ length: amt + 1 }, () => 0) ); // 初始化首列 for (let i = 0; i <= n; i++) { dp[i][0] = 1; } // 状态转移 for (let i = 1; i <= n; i++) { for (let a = 1; a <= amt; a++) { if (coins[i - 1] > a) { // 若超过目标金额,则不选硬币 i dp[i][a] = dp[i - 1][a]; } else { // 不选和选硬币 i 这两种方案之和 dp[i][a] = dp[i - 1][a] + dp[i][a - coins[i - 1]]; } } } return dp[n][amt]; } ``` === "TS" ```typescript title="coin_change_ii.ts" /* 零钱兑换 II:动态规划 */ function coinChangeIIDP(coins: Array, amt: number): number { const n = coins.length; // 初始化 dp 表 const dp = Array.from({ length: n + 1 }, () => Array.from({ length: amt + 1 }, () => 0) ); // 初始化首列 for (let i = 0; i <= n; i++) { dp[i][0] = 1; } // 状态转移 for (let i = 1; i <= n; i++) { for (let a = 1; a <= amt; a++) { if (coins[i - 1] > a) { // 若超过目标金额,则不选硬币 i dp[i][a] = dp[i - 1][a]; } else { // 不选和选硬币 i 这两种方案之和 dp[i][a] = dp[i - 1][a] + dp[i][a - coins[i - 1]]; } } } return dp[n][amt]; } ``` === "Dart" ```dart title="coin_change_ii.dart" /* 零钱兑换 II:动态规划 */ int coinChangeIIDP(List coins, int amt) { int n = coins.length; // 初始化 dp 表 List> dp = List.generate(n + 1, (index) => List.filled(amt + 1, 0)); // 初始化首列 for (int i = 0; i <= n; i++) { dp[i][0] = 1; } // 状态转移 for (int i = 1; i <= n; i++) { for (int a = 1; a <= amt; a++) { if (coins[i - 1] > a) { // 若超过目标金额,则不选硬币 i dp[i][a] = dp[i - 1][a]; } else { // 不选和选硬币 i 这两种方案之和 dp[i][a] = dp[i - 1][a] + dp[i][a - coins[i - 1]]; } } } return dp[n][amt]; } ``` === "Rust" ```rust title="coin_change_ii.rs" /* 零钱兑换 II:动态规划 */ fn coin_change_ii_dp(coins: &[i32], amt: usize) -> i32 { let n = coins.len(); // 初始化 dp 表 let mut dp = vec![vec![0; amt + 1]; n + 1]; // 初始化首列 for i in 0..=n { dp[i][0] = 1; } // 状态转移 for i in 1..=n { for a in 1..=amt { if coins[i - 1] > a as i32 { // 若超过目标金额,则不选硬币 i dp[i][a] = dp[i - 1][a]; } else { // 不选和选硬币 i 这两种方案之和 dp[i][a] = dp[i - 1][a] + dp[i][a - coins[i - 1] as usize]; } } } dp[n][amt] } ``` === "C" ```c title="coin_change_ii.c" /* 零钱兑换 II:动态规划 */ int coinChangeIIDP(int coins[], int amt, int coinsSize) { int n = coinsSize; // 初始化 dp 表 int **dp = malloc((n + 1) * sizeof(int *)); for (int i = 0; i <= n; i++) { dp[i] = calloc(amt + 1, sizeof(int)); } // 初始化首列 for (int i = 0; i <= n; i++) { dp[i][0] = 1; } // 状态转移 for (int i = 1; i <= n; i++) { for (int a = 1; a <= amt; a++) { if (coins[i - 1] > a) { // 若超过目标金额,则不选硬币 i dp[i][a] = dp[i - 1][a]; } else { // 不选和选硬币 i 这两种方案之和 dp[i][a] = dp[i - 1][a] + dp[i][a - coins[i - 1]]; } } } int res = dp[n][amt]; // 释放内存 for (int i = 0; i <= n; i++) { free(dp[i]); } free(dp); return res; } ``` === "Kotlin" ```kotlin title="coin_change_ii.kt" /* 零钱兑换 II:动态规划 */ fun coinChangeIIDP(coins: IntArray, amt: Int): Int { val n = coins.size // 初始化 dp 表 val dp = Array(n + 1) { IntArray(amt + 1) } // 初始化首列 for (i in 0..n) { dp[i][0] = 1 } // 状态转移 for (i in 1..n) { for (a in 1..amt) { if (coins[i - 1] > a) { // 若超过目标金额,则不选硬币 i dp[i][a] = dp[i - 1][a] } else { // 不选和选硬币 i 这两种方案之和 dp[i][a] = dp[i - 1][a] + dp[i][a - coins[i - 1]] } } } return dp[n][amt] } ``` === "Ruby" ```ruby title="coin_change_ii.rb" [class]{}-[func]{coin_change_ii_dp} ``` === "Zig" ```zig title="coin_change_ii.zig" // 零钱兑换 II:动态规划 fn coinChangeIIDP(comptime coins: []i32, comptime amt: usize) i32 { comptime var n = coins.len; // 初始化 dp 表 var dp = [_][amt + 1]i32{[_]i32{0} ** (amt + 1)} ** (n + 1); // 初始化首列 for (0..n + 1) |i| { dp[i][0] = 1; } // 状态转移 for (1..n + 1) |i| { for (1..amt + 1) |a| { if (coins[i - 1] > @as(i32, @intCast(a))) { // 若超过目标金额,则不选硬币 i dp[i][a] = dp[i - 1][a]; } else { // 不选和选硬币 i 这两种方案的较小值 dp[i][a] = dp[i - 1][a] + dp[i][a - @as(usize, @intCast(coins[i - 1]))]; } } } return dp[n][amt]; } ``` ??? pythontutor "Code Visualization"
### 3.   Space optimization The space optimization approach is the same, just remove the coin dimension: === "Python" ```python title="coin_change_ii.py" def coin_change_ii_dp_comp(coins: list[int], amt: int) -> int: """零钱兑换 II:空间优化后的动态规划""" n = len(coins) # 初始化 dp 表 dp = [0] * (amt + 1) dp[0] = 1 # 状态转移 for i in range(1, n + 1): # 正序遍历 for a in range(1, amt + 1): if coins[i - 1] > a: # 若超过目标金额,则不选硬币 i dp[a] = dp[a] else: # 不选和选硬币 i 这两种方案之和 dp[a] = dp[a] + dp[a - coins[i - 1]] return dp[amt] ``` === "C++" ```cpp title="coin_change_ii.cpp" /* 零钱兑换 II:空间优化后的动态规划 */ int coinChangeIIDPComp(vector &coins, int amt) { int n = coins.size(); // 初始化 dp 表 vector dp(amt + 1, 0); dp[0] = 1; // 状态转移 for (int i = 1; i <= n; i++) { for (int a = 1; a <= amt; a++) { if (coins[i - 1] > a) { // 若超过目标金额,则不选硬币 i dp[a] = dp[a]; } else { // 不选和选硬币 i 这两种方案之和 dp[a] = dp[a] + dp[a - coins[i - 1]]; } } } return dp[amt]; } ``` === "Java" ```java title="coin_change_ii.java" /* 零钱兑换 II:空间优化后的动态规划 */ int coinChangeIIDPComp(int[] coins, int amt) { int n = coins.length; // 初始化 dp 表 int[] dp = new int[amt + 1]; dp[0] = 1; // 状态转移 for (int i = 1; i <= n; i++) { for (int a = 1; a <= amt; a++) { if (coins[i - 1] > a) { // 若超过目标金额,则不选硬币 i dp[a] = dp[a]; } else { // 不选和选硬币 i 这两种方案之和 dp[a] = dp[a] + dp[a - coins[i - 1]]; } } } return dp[amt]; } ``` === "C#" ```csharp title="coin_change_ii.cs" /* 零钱兑换 II:空间优化后的动态规划 */ int CoinChangeIIDPComp(int[] coins, int amt) { int n = coins.Length; // 初始化 dp 表 int[] dp = new int[amt + 1]; dp[0] = 1; // 状态转移 for (int i = 1; i <= n; i++) { for (int a = 1; a <= amt; a++) { if (coins[i - 1] > a) { // 若超过目标金额,则不选硬币 i dp[a] = dp[a]; } else { // 不选和选硬币 i 这两种方案之和 dp[a] = dp[a] + dp[a - coins[i - 1]]; } } } return dp[amt]; } ``` === "Go" ```go title="coin_change_ii.go" /* 零钱兑换 II:空间优化后的动态规划 */ func coinChangeIIDPComp(coins []int, amt int) int { n := len(coins) // 初始化 dp 表 dp := make([]int, amt+1) dp[0] = 1 // 状态转移 for i := 1; i <= n; i++ { // 正序遍历 for a := 1; a <= amt; a++ { if coins[i-1] > a { // 若超过目标金额,则不选硬币 i dp[a] = dp[a] } else { // 不选和选硬币 i 这两种方案之和 dp[a] = dp[a] + dp[a-coins[i-1]] } } } return dp[amt] } ``` === "Swift" ```swift title="coin_change_ii.swift" /* 零钱兑换 II:空间优化后的动态规划 */ func coinChangeIIDPComp(coins: [Int], amt: Int) -> Int { let n = coins.count // 初始化 dp 表 var dp = Array(repeating: 0, count: amt + 1) dp[0] = 1 // 状态转移 for i in 1 ... n { for a in 1 ... amt { if coins[i - 1] > a { // 若超过目标金额,则不选硬币 i dp[a] = dp[a] } else { // 不选和选硬币 i 这两种方案之和 dp[a] = dp[a] + dp[a - coins[i - 1]] } } } return dp[amt] } ``` === "JS" ```javascript title="coin_change_ii.js" /* 零钱兑换 II:状态压缩后的动态规划 */ function coinChangeIIDPComp(coins, amt) { const n = coins.length; // 初始化 dp 表 const dp = Array.from({ length: amt + 1 }, () => 0); dp[0] = 1; // 状态转移 for (let i = 1; i <= n; i++) { for (let a = 1; a <= amt; a++) { if (coins[i - 1] > a) { // 若超过目标金额,则不选硬币 i dp[a] = dp[a]; } else { // 不选和选硬币 i 这两种方案之和 dp[a] = dp[a] + dp[a - coins[i - 1]]; } } } return dp[amt]; } ``` === "TS" ```typescript title="coin_change_ii.ts" /* 零钱兑换 II:状态压缩后的动态规划 */ function coinChangeIIDPComp(coins: Array, amt: number): number { const n = coins.length; // 初始化 dp 表 const dp = Array.from({ length: amt + 1 }, () => 0); dp[0] = 1; // 状态转移 for (let i = 1; i <= n; i++) { for (let a = 1; a <= amt; a++) { if (coins[i - 1] > a) { // 若超过目标金额,则不选硬币 i dp[a] = dp[a]; } else { // 不选和选硬币 i 这两种方案之和 dp[a] = dp[a] + dp[a - coins[i - 1]]; } } } return dp[amt]; } ``` === "Dart" ```dart title="coin_change_ii.dart" /* 零钱兑换 II:空间优化后的动态规划 */ int coinChangeIIDPComp(List coins, int amt) { int n = coins.length; // 初始化 dp 表 List dp = List.filled(amt + 1, 0); dp[0] = 1; // 状态转移 for (int i = 1; i <= n; i++) { for (int a = 1; a <= amt; a++) { if (coins[i - 1] > a) { // 若超过目标金额,则不选硬币 i dp[a] = dp[a]; } else { // 不选和选硬币 i 这两种方案之和 dp[a] = dp[a] + dp[a - coins[i - 1]]; } } } return dp[amt]; } ``` === "Rust" ```rust title="coin_change_ii.rs" /* 零钱兑换 II:空间优化后的动态规划 */ fn coin_change_ii_dp_comp(coins: &[i32], amt: usize) -> i32 { let n = coins.len(); // 初始化 dp 表 let mut dp = vec![0; amt + 1]; dp[0] = 1; // 状态转移 for i in 1..=n { for a in 1..=amt { if coins[i - 1] > a as i32 { // 若超过目标金额,则不选硬币 i dp[a] = dp[a]; } else { // 不选和选硬币 i 这两种方案之和 dp[a] = dp[a] + dp[a - coins[i - 1] as usize]; } } } dp[amt] } ``` === "C" ```c title="coin_change_ii.c" /* 零钱兑换 II:空间优化后的动态规划 */ int coinChangeIIDPComp(int coins[], int amt, int coinsSize) { int n = coinsSize; // 初始化 dp 表 int *dp = calloc(amt + 1, sizeof(int)); dp[0] = 1; // 状态转移 for (int i = 1; i <= n; i++) { for (int a = 1; a <= amt; a++) { if (coins[i - 1] > a) { // 若超过目标金额,则不选硬币 i dp[a] = dp[a]; } else { // 不选和选硬币 i 这两种方案之和 dp[a] = dp[a] + dp[a - coins[i - 1]]; } } } int res = dp[amt]; // 释放内存 free(dp); return res; } ``` === "Kotlin" ```kotlin title="coin_change_ii.kt" /* 零钱兑换 II:空间优化后的动态规划 */ fun coinChangeIIDPComp(coins: IntArray, amt: Int): Int { val n = coins.size // 初始化 dp 表 val dp = IntArray(amt + 1) dp[0] = 1 // 状态转移 for (i in 1..n) { for (a in 1..amt) { if (coins[i - 1] > a) { // 若超过目标金额,则不选硬币 i dp[a] = dp[a] } else { // 不选和选硬币 i 这两种方案之和 dp[a] = dp[a] + dp[a - coins[i - 1]] } } } return dp[amt] } ``` === "Ruby" ```ruby title="coin_change_ii.rb" [class]{}-[func]{coin_change_ii_dp_comp} ``` === "Zig" ```zig title="coin_change_ii.zig" // 零钱兑换 II:空间优化后的动态规划 fn coinChangeIIDPComp(comptime coins: []i32, comptime amt: usize) i32 { comptime var n = coins.len; // 初始化 dp 表 var dp = [_]i32{0} ** (amt + 1); dp[0] = 1; // 状态转移 for (1..n + 1) |i| { for (1..amt + 1) |a| { if (coins[i - 1] > @as(i32, @intCast(a))) { // 若超过目标金额,则不选硬币 i dp[a] = dp[a]; } else { // 不选和选硬币 i 这两种方案的较小值 dp[a] = dp[a] + dp[a - @as(usize, @intCast(coins[i - 1]))]; } } } return dp[amt]; } ``` ??? pythontutor "Code Visualization"