* 动态规划
要注意动态规划和分而治之(归并排序和快速排序算法中用到的那种)是不 同的方法。
分而治之方法是把问题分解成相互独立的子问题,然后组合它们的答 案,
而动态规划则是将问题分解成相互依赖的子问题。
用动态规划解决问题时,要遵循三个重要步骤:
(1) 定义子问题;
(2) 实现要反复执行而解决子问题的部分(这一步要参考前一节讨论的递归的步骤);
(3) 识别并求解出边界条件。
最少硬币找零问题是硬币找零问题的一个变种。硬币找零问题是给出要找零的钱数,以及可 用的硬币面额d1...dn及其数量,找出有多少种找零方法。最少硬币找零问题是给出要找零的钱数, 以及可用的硬币面额d1...dn及其数量,找到所需的最少的硬币个数。
例如,美国有以下面额(硬币):d1=1,d2=5,d3=10,d4=25。
如果要找36美分的零钱,我们可以用1个25美分、1个10美分和1个便士(1美分)。
* MinCoinChange.js
/**
* Created by Mch on 10/4/18.
*/
function MinCoinChange(/* Array */ coins) {
"use strict";
this.coins = coins;
this.cache = {};
}
/**
* @param amount Number
* @returns {*}
*/
MinCoinChange.prototype.makeChange = function (amount) {
"use strict";
var me = this, min = [], newMin, newAmount;
if (!amount) { return []; }
if (this.cache[amount]) { return this.cache[amount]; }
this.coins.forEach(function(coin) {
newAmount = amount - coin;
if (newAmount >= 0) {
newMin = me.makeChange(newAmount);
}
if (newAmount >= 0 &&
(newMin.length < min.length - 1 || !min.length) &&
(newMin.length || !newAmount)) {
min = [ coin ].concat(newMin);
console.debug("new Min " + min + ' for ' + amount);
}
});
return (this.cache[amount] = min);
};
exports.MinCoinChange = MinCoinChange;
* index.js test
/**
* Created by Mch on 10/4/18.
*/
var MinCoinChange = require ('./MinCoinChange.js').MinCoinChange;
var minCoinChange = new MinCoinChange([1, 5, 10, 25]);
console.log(minCoinChange.makeChange(36));
$ node index.js
new Min 1 for 1
new Min 1,1 for 2
new Min 1,1,1 for 3
new Min 1,1,1,1 for 4
new Min 1,1,1,1,1 for 5
new Min 5 for 5
new Min 1,5 for 6
new Min 1,1,5 for 7
new Min 1,1,1,5 for 8
new Min 1,1,1,1,5 for 9
new Min 1,1,1,1,1,5 for 10
new Min 5,5 for 10
new Min 10 for 10
new Min 1,10 for 11
new Min 1,1,10 for 12
new Min 1,1,1,10 for 13
new Min 1,1,1,1,10 for 14
new Min 1,1,1,1,1,10 for 15
new Min 5,10 for 15
new Min 1,5,10 for 16
new Min 1,1,5,10 for 17
new Min 1,1,1,5,10 for 18
new Min 1,1,1,1,5,10 for 19
new Min 1,1,1,1,1,5,10 for 20
new Min 5,5,10 for 20
new Min 10,10 for 20
new Min 1,10,10 for 21
new Min 1,1,10,10 for 22
new Min 1,1,1,10,10 for 23
new Min 1,1,1,1,10,10 for 24
new Min 1,1,1,1,1,10,10 for 25
new Min 5,10,10 for 25
new Min 25 for 25
new Min 1,25 for 26
new Min 1,1,25 for 27
new Min 1,1,1,25 for 28
new Min 1,1,1,1,25 for 29
new Min 1,1,1,1,1,25 for 30
new Min 5,25 for 30
new Min 1,5,25 for 31
new Min 1,1,5,25 for 32
new Min 1,1,1,5,25 for 33
new Min 1,1,1,1,5,25 for 34
new Min 1,1,1,1,1,5,25 for 35
new Min 5,5,25 for 35
new Min 10,25 for 35
new Min 1,10,25 for 36
[ 1, 10, 25 ]
* MinCoins.php
<?php
class MinCoins {
const MAX_INTEGER = 2147483647;
/**
* @param array $arr
* @param int $aim
* @return int
*/
public static function minCoins1(array $arr, int $aim) {
if (!is_array($arr) || count($arr)===0 || is_null($arr)) {
return -1;
}
$n = count($arr);
$max = self::MAX_INTEGER;
$dp = new SplFixedArray($n);
for ($i = 0; $i < $dp->getSize(); $i++) {
$dp->offsetSet($i, new SplFixedArray($aim+1));
}
for ($j = 1; $j <= $aim; $j++) {
$dp->offsetGet(0)->offsetSet($j, $max);
if ($j - $arr[0] >= 0 && $dp->offsetGet(0)->offsetGet($j-$arr[0]) !== $max) {
$dp->offsetGet(0)->offsetSet($j, $dp->offsetGet(0)->offsetGet($j-$arr[0])+1);
}
}
for ($i = 1; $i < $n; $i++) {
for ($j = 1; $j <= $aim; $j++) {
$left = $max;
if ($j - $arr[$i]>=0 && $dp->offsetGet($i)->offsetGet($j-$arr[$i]) !== $max) {
$left = $dp->offsetGet($i)->offsetGet($j-$arr[$i]) + 1;
}
$dp->offsetGet($i)->offsetSet($j, min($left, $dp->offsetGet($i-1)->offsetGet($j)));
}
}
return $dp->offsetGet($n-1)->offsetGet($aim) !== $max ?
$dp->offsetGet($n-1)->offsetGet($aim) : -1;
}
// 空间压缩
public static function minCoins2(array $arr, int $aim) {
if (!is_array($arr) || count($arr)===0 || is_null($arr)) {
return -1;
}
$n = count($arr);
$max = self::MAX_INTEGER;;
$dp = new SplFixedArray($aim + 1);
for ($j = 1; $j <= $aim; $j++) {
$dp[$j] = $max;
if ($j-$arr[0]>=0 && $dp[$j-$arr[0]] !== $max) {
$dp[$j] = $dp[$j-$arr[0]] + 1;
}
}
for ($i = 1; $i < $n; $i++) {
for ($j = 1; $j <= $aim; $j++) {
$left = $max;
if ($j-$arr[$i]>=0 && $dp[$j-$arr[$i]] !== $max) {
$left = $dp[$j-$arr[$i]] + 1;
}
$dp[$j] = min($left, $dp[$j]);
}
}
return $dp[$aim] !== $max ? $dp[$aim]: -1;
}
public static function minCoins3(array $arr, int $aim) {
if (!is_array($arr) || count($arr)===0 || is_null($arr)) {
return -1;
}
$max = self::MAX_INTEGER;;
$dp = new SplFixedArray($aim+1);
for ($j = 1; $j <= $aim; $j++) {
$dp[$j] = $max;
}
if ($arr[0] <= $aim) {
$dp[$arr[0]] = 1;
}
// 左上角的某个位置
for ($i = 1, $n = count($arr); $i < $n; $i++) {
for ($j = $aim; $j > 0; $j--) {
$leftup = $max;
if ($j-$arr[$i]>=0 && $dp[$j-$arr[$i]] !== $max) {
$leftup = $dp[$j-$arr[$i]]+1;
}
$dp[$j] = min($leftup, $dp[$j]);
}
}
return $dp[$aim] !== $max ? $dp[$aim] : -1;
}
}
* test:
<?php
echo MinCoins::minCoins1([1,5,10,20,50], 93).PHP_EOL; // 6
echo MinCoins::minCoins2([1,5,10,20,50], 93).PHP_EOL; // 6
// SplFixedArray 赋值, 遍历 用法测试
// http://php.net/manual/en/class.splfixedarray.php