LeetCode week 14: Coin Change

来源:互联网 发布:自己在知乎匿名 编辑:程序博客网 时间:2024/04/29 16:37

题目

地址: https://leetcode.com/problems/coin-change/description/
类别: 动态规划
难度: Medium
描述:

You are given coins of different denominations and a total amount of money amount. Write a function to compute the fewest number of coins that you need to make up that amount. If that amount of money cannot be made up by any combination of the coins, return -1.

Example 1:

coins = [1, 2, 5], amount = 11return 3 (11 = 5 + 5 + 1)

Example 2:

coins = [2], amount = 3return -1.

Note:
You may assume that you have an infinite number of each kind of coin.

分析

给定含n种面值不同的硬币,每种硬币皆有无限多个,判断能否用这些硬币凑出总金额amount,若不能,则返回-1;若能,则返回所需最少硬币数量。
例:

Input: coins = [1, 2, 5], amount = 11

硬币最少组合:

11=5+5+1

因此:

Output: 3

思路

一、动态规划

定义子问题:

dp[i][j]:用前i种硬币组成金额j的所需硬币最少数量coins[i-1]:第i种硬币的币值

最终目标:

dp[n][amount]

状态迁移方程:

求dp[i][j]时: 1. coins[i-1] <= j && dp[i][j-coins[i-1]] != -1,这意味着能用第i种硬币。此时分两种情况:     (1)dp[i-1][j] == -1,能用且必须用,因为只用前 i-1 种硬币无法组成j, 此时共有1+ dp[i][j-coins[i-1]]个硬币;     (2)dp[i-1][j] != -1,能用但并非必须用,此时需要比较用和不用的情况哪个更少:dp[i][j] = min(dp[i][j-coins[i-1]] + 1, dp[i-1][j]); 2. !(coins[i-1] <= j && dp[i][j-coins[i-1]] != -1),这意味着不能用第i种硬币,此时取决于dp[i-1][j]。

代码:

class Solution {public:    int coinChange(vector<int>& coins, int amount) {        vector<vector<int>> dp(coins.size()+1, vector<int>(amount+1, amount+1));        dp[0][0] = 0;        for (int j = 1; j <=amount; j++) dp[0][j] = -1;        for (int i = 1; i <= coins.size(); i++) {            dp[i][0] = 0;            for (int j = 1; j <= amount; j++) {                if(coins[i-1] <= j && dp[i][j-coins[i-1]] != -1) {                    if(dp[i-1][j] != -1) dp[i][j] = min(dp[i][j-coins[i-1]] + 1, dp[i-1][j]);                    else dp[i][j] = dp[i][j-coins[i-1]] + 1;                } else dp[i][j] = dp[i-1][j];            }        }        return dp[coins.size()][amount];    }};

因为dp[i][j] 只依赖于dp[i-1][j] 和 dp[i][j-coins[i-1]],所以我们可以用一维数组优化空间如下:

class Solution {public:    int coinChange(vector<int>& coins, int amount) {        vector<int> a(amount+1, amount+1);        a[0] = 0;        for(auto c:coins){            for(int i=c;i<=amount;i++){               a[i]=min(a[i],a[i-c]+1);            }        }        return a[amount]==amount+1?-1:a[amount];    }};

二、BFS

这里写图片描述
如上图所示,共有3个币种{1,2,5},我们由根节点开始构造树,每个节点都有3个子节点,且连接3个子节点的边权值为3种币值,每个节点的值为从根节点到该节点的路径边之和。在构造该树时,若产生的新节点之值在现有节点中存在,则舍去该点。
不难发现,我们不断生成的新节点值正是这些硬币的组合,而且节点的深度等于硬币个数。同时,舍去重复值点既防止了指数爆炸,又与硬币数最少这一要求契合。
在生成该树的过程中,若发现了与amount相同的值点,则返回其深度;若在某一层的最小值都比amount大(注意到每一层的最小值小于下一层的所有值),则说明无法组合成amount。
代码:

class Solution {public:    int coinChange(vector<int>& coins, int amount) {        unordered_set<int> reachable;        queue<int> q;        for (auto c : coins)        {            reachable.insert(c);            q.push(c);        }        int size = coins.size();        int height = 1;        int levelNodes = size;        int nodes;        int value;        int min_in_level;        while(!q.empty()) {            min_in_level = INT_MAX;            nodes = 0;            for (int i = 0; i < levelNodes; ++i)            {                value = q.front();                if(amount == 0) return 0;                if(value == amount) return height;                q.pop();                for (int i = 0; i < size; ++i)                {                    if(reachable.count(coins[i] + value) == 0) {                        reachable.insert(coins[i] + value);                        q.push(coins[i] + value);                        nodes = nodes + 1;                    }                }                min_in_level = min(min_in_level, value);            }            if(min_in_level > amount) return -1;            levelNodes = nodes;            height = height + 1;        }    }};