动态规划

遇到动态规划的一般解题思路:

1. 确定步骤

状态在动态规划中的作用属于定海神针。

简单来说, 解动态规划的时候需要开一个数组, 数组的每个元素f【i】或者f【i】【j】代表什么

确定状态需要两个意识:

  • 最后一步
  • 子问题
2. 转移方程

f【x】 = f【x-1】

3. 初始化条件和边界情况

初始条件, 用转移方程算不出来, 需要手动定义
边界情况, 数组不要越界,向上越界,向下越界

4. 计算顺序

当我们计算到f【x】时, 右边的都已经得到结果了

 

 

举个例子:

  • 你有三种硬币, 分别面值2元, 5元和7元, 每种硬币都有足够多
  • 买一本书需要27元
  • 如何用最少的硬币组合正好付清, 不需要对方找钱

拆分:

最后一步

虽然我们不知道最优策略是什么, 但是最优策略肯定是K枚硬币a1, a2... ak面值加起来是27

所以一定有一枚最后的硬币:ak

除掉这枚硬币, 前面的面值加起来是27 - ak

关键点

我们不关心前面的K-1枚硬币是怎么拼出27-ak的(可能有一种拼法, 可能有100中拼法), 而且我们现在甚至还不知道ak和K, 但是我们确定前面的硬币拼出了27-ak

因为是最优策略, 所以拼出27-ak的硬币数一定要最少, 否则这就不是最优策略了

子问题

所以我们就要求: 最少用多少枚硬币可以拼出27-ak
原问题是最少用多少问题拼出27
我们将原问题转化成了一个子问题, 而且规模更小: 27-ak
为了简化定义, 我们设状态f(x) = 最少用多少枚硬币拼出x

等等,我们还不知道最后那一枚硬币ak是多少
最后那枚硬币ak只可能是2,5或者7
如果ak是2,f(27)应该是f(27-2)+1(加上最后这一枚硬币2)
如果ak是5,f(27)应该是f(27-5)+1(加上最后这一枚硬币5)
如果ak是7,f(27)应该是f(27-7)+1(加上最后这一枚硬币7)
除此之外, 没有其他的可能了
需要求最少的硬币数,所以:
f(27) = min(f(27-2)+1, f(27-5)+1, f(27-7)+1)

转移方程

设状态f【X】=最少用多少枚硬币拼出X
对于任意X
f【x】= min{ f【x-2】+1, f【x-5】+1, f【x-7】+1 }

初始化条件和边界情况

f【x】= min{ f【x-2】+1, f【x-5】+1, f【x-7】+1 }
两个问题

  • x-2,x-5或者x-7小于0怎么办?
  • 什么时候停下来

如果不能拼出Y,就定义f【Y】=正无穷, 例如f【-1】=f【-2】 = 。。。 = 正无穷
所以f【1】 = min【f【-1】+1,f【-4】+1, f【-6】+1】 = 正无穷, 表示拼不出来1

初始条件: f【0】= 0

 function coinChange(A, M){
    var f = new Array(M+1);
    // f.fill(0,0, f.length) // 用0初始化数组
    var n = A.length // number of kinds of coins
    // initialization
    // console.log(Number.MAX_VALUE)
    f[0] = 0;
    var i, j;
    // f[1], f[2], .... f[27]
    for(i = 1; i <= M; ++i){
      f[i] = Number.MAX_VALUE;//某一个是正无穷
      // last coin a[j]
      for(j = 0; j <= n; ++j){ //j是硬币类型
        if(i >= A[j] && f[i - A[j]] != Number.MAX_VALUE){ //A[j]是最后一枚硬币面额
          var a = f[i - A[j]] + 1;//递归
          var b = f[i];//递归
          f[i] = Math.min.apply(null, [a, b])
        }

      }
    }
    console.log(f)
    if(f[M] == Number.MAX_VALUE){
      return -1;
    }
    return f[M]
  }

  console.log(coinChange([2, 5, 7], 27));

 

posted @ 2021-07-21 15:49  菜鸟小何  阅读(82)  评论(0编辑  收藏  举报