这道题和coin change II, combination sum, combination sum IV都一块儿去看, 总结一下.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 class Solution { public int coinChange (int [] coins, int amount) { int [] dp = new int [amount + 1 ]; Arrays.fill(dp, amount + 1 ); dp[0 ] = 0 ; for (int i = 1 ; i < dp.length; i++) { for (int coin : coins) { if (coin <= i) { dp[i] = Math.min(dp[i], dp[i - coin] + 1 ); } } } return dp[amount] > amount ? -1 : dp[amount]; } }
这道题典型的DP. 对于某个amount, 以及给定的coins, 如何到达这个amount? 肯定有这么一枚coin, 加上它之后, 就到达这个amount. 那么到达这个amount的最后一步可以是加上coins里面的任意一个coin. 于是我们想知道dp[amount], 就得知道dp[amount - coin1], dp[amount - coin12], dp[amount - coin3]… dp[amount - coinN]中哪个最小, 最小的那个 + 1(即加上最后一个coin到达dp[amount])就是答案.
我们一开始把dp里面所有的元素都初始为amount + 1, 因为这个数字是不可能的, 即使最小的硬币是1, 最少的钱数也是amount. 因此用amount + 1表示凑不到. dp[0]初始为0表示0个coin就可以凑到, 这也是符合常识的, 有时候dp初始几个元素的赋值是不符合常识的, 而是需要谨慎选择的. 在进行inner循环的时候, 如何coin的值大于当前的amount, 那就说明无法在使用当前的coin的前提下凑到目标amount.
如何保证dp[i - coin]是我们遍历过的. 首先 coin <= i保证dp[i - coin]不出界. 其次由于coin > 0, i - coin肯定小于i, 因此在当前位置的左侧. 既然在左侧, 那么我们一定check过. 所以就不用担心dp[i - coin]没有被遍历过了.
时间复杂度: O(S * N) S是amount, N是coin的数量. 空间复杂度: O(S) 因为我们用一个数组去存到每一个amount最小的coin的个数.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 class Solution { public int coinChange (int [] coins, int amount) { return helper(coins, amount, new int [amount + 1 ]); } private int helper (int [] coins, int amount, int [] cache) { if (amount < 0 ) return -1 ; if (amount == 0 ) return 0 ; if (cache[amount] != 0 ) return cache[amount]; int min = Integer.MAX_VALUE; for (int coin : coins) { int ans = helper(coins, amount - coin, cache); if (ans >= 0 && ans < min) min = ans + 1 ; } cache[amount] = min == Integer.MAX_VALUE ? -1 : min; return cache[amount]; } }
这个是DFS. 递归函数的功能就是给一个amount, 就能告诉我们最少数目的coins是多少, 如果凑不到返回-1. 我们使用了一个cache来去装我们遇到过的答案, 这样大大减少递归的调用.
时间复杂度: O(S * N) 空间复杂度: O(S) 假设有个coin是1, 那么最深就要S个函数frames.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 class Solution { public int coinChange (int [] coins, int amount) { int [] visited = new int [amount + 1 ]; Arrays.fill(visited, amount + 1 ); visited[0 ] = 0 ; helper(coins, amount, visited); return visited[amount] < amount + 1 ? visited[amount] : -1 ; } private int helper (int [] coins, int amount, int [] visited) { if (visited[amount] < amount + 1 ) return visited[amount]; for (int coin : coins) { if (coin <= amount) { visited[amount] = Math.min(visited[amount], helper(coins, amount - coin, visited) + 1 ); } } return visited[amount]; } }
比较简洁的写法.