动态规划:从入门到放弃

栏目: 编程工具 · 发布时间: 5年前

内容简介:动态规划(DP, Dynamic Programming)一句话总结:常见问题

动态规划(DP, Dynamic Programming)

一句话总结: 在求解一个复杂问题时,将其分解为若干个简单问题。通过求解简单问题的最优解,来找到目标问题的最优解。

常见问题

我们通过一个例子来了解一下DP的基本原理。

首先,我们要找到某个状态的最优解,然后在它的帮助下,找到下一个状态的最优解。

如硬币问题的例子

硬币问题:如果我们有面值为1元、3元和5元的硬币若干枚,如何用最少的硬币凑够11元?

首先我们将该问题分解为

  1. 如何用最少的硬币凑够0元?
  2. 如何用最少的硬币凑够1元?
  3. 如何用最少的硬币凑够2元?
  4. 如何用最少的硬币凑够11元?

“状态”用来描述该问题的子问题的解。

显然,第1个问题第解是0,我们只需要0个硬币就能凑够0元。

我们用 $d(i)=j$ 来表示凑够 $i$ 元至少需要 $j$ 个硬币

第1个问题即

$$d(0)=0$$

我们在解决第2个问题时(如何用最少的硬币凑够1元?),我们可以结合第1个问题第最优解,来解出第2个问题。

凑出1元时,我们可选的硬币只有1元硬币,我们只需挑选1个1元硬币,结合第1个问题第最优解即可求出第2个问题,即

$$d(1)=d(1-1)+1=d(0)+1=0+1=1$$

同理,凑出2元时,我们仍然只有1元硬币可用,于是再挑选1个1元硬币,结合第二个问题第最优解来求出第三个问题,即

$$d(2)=d(2-1)+1=d(1)+1=1+1=2$$

凑出3元时,我们多了一种3元硬币可选,于是我们就有2种方案可选:

  1. 拿起1元硬币

    如果我们拿起1元硬币,我们的目标就变成了:凑够3-1元需要的最少硬币数量,即

    $$d(3)=d(3-1)+1=d(2)+1=2+1=3$$

  2. 拿起3元硬币

    如果我们拿起3元硬币,我们的目标就变成:凑够3-3=0元需要的最少硬币数量,即

    $$d(3)=d(3-3)+1=d(0)+1=0+1=1$$

所以我们得到

$$d(3)=\min\{d(3-1)+1, d(3-3)+1\}$$

从上面的演算中,我们抽出两个概念: 状态状态转移方程

上文中 $d(i)$ 表示凑够 $i$ 元需要的最少硬币数量,我们定义为该问题的“状态”。

我们最终要求解的问题可以用这个状态来表示: $d(3)$ 即凑够3元最少需要多少硬币。

状态转移方程就是

$$d(3)=\min\{d(3-1)+1, d(3-3)+1\}$$

它描述了状态之间时如何转移的,我们对它抽象化

$$d(i)=\min\{d(i-v_j)+1\}$$

其中 $i-v_j \geq 0$, $v_j$ 表示第 $j$ 个硬币的面值

有了状态和状态转移方程,这个问题基本上就解决了

下面是当 i 从 0 到 11 时到解

$i$ $j$ $v_j$ ($\min\{d(i-v_j)\}$)
0 0 -
1 1 1 (0)
2 2 1 (1)
3 1 3 (0)
4 2 1 (3)
5 1 5 (0)
6 2 3 (3)
7 3 1 (6)
8 2 3 (5)
9 3 1 (8)
10 2 5 (5)
11 3 1 (10)

可以得到,要凑够11元至少需要3枚硬币

$$ d(11)=d(10)+1=d(5)+1+1=d(0)+1+1+1=3 $$

BB 这么多没用, Show your code !

Leetcode 322. Coin Change
// CoinChange: coins 硬币, amount 期望的金额, 返回最少需要的硬币数量,如果不可解返回-1
func CoinChange(coins []int, amount int) int {
  dp := make([]int, amount+1)
  dp[0] = 0

  for i := 1; i <= amount; i++ {
    dp[i] = amount + 1
    for _, coin := range coins {
      if coin <= i && dp[i-coin] != -1 && dp[i-coin]+1 < dp[i] {
        dp[i] = dp[i-coin] + 1
      }
    }
    if dp[i] > amount {
      dp[i] = -1
    }
  }

  return dp[amount]
}
import "testing"

func TestCoinCharge(t *testing.T) {
  type args struct {
    coins  []int
    amount int
  }
  tests := []struct {
    name string
    args args
    want int
  }{
    {"[2] => 3", args{[]int{2}, 3}, -1},
    {"[2] => 4", args{[]int{2}, 4}, 2},
    {"[1,2,5] => 11", args{[]int{1, 2, 5}, 11}, 3},
    {"[1,3,5] => 11", args{[]int{1, 3, 5}, 11}, 3},
  }
  for _, tt := range tests {
    t.Run(tt.name, func(t *testing.T) {
      if got := CoinCharge(tt.args.coins, tt.args.amount); got != tt.want {
        t.Errorf("CoinCharge() = %v, want %v", got, tt.want)
      }
    })
  }
}
Runtime: 8 ms, faster than 99.26% of Go online submissions for Coin Change.

先写这么多,有机会再深入了解高阶的动态规划问题


以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

数据挖掘导论

数据挖掘导论

(美)Pang-Ning Tan、Michael Steinbach、Vipin Kumar / 机械工业出版社 / 2010-9 / 59.00元

本书全面介绍了数据挖掘的理论和方法,着重介绍如何用数据挖掘知识解决各种实际问题,涉及学科领域众多,适用面广。 书中涵盖5个主题:数据、分类、关联分析、聚类和异常检测。除异常检测外,每个主题都包含两章:前面一章讲述基本概念、代表性算法和评估技术,后面一章较深入地讨论高级概念和算法。目的是使读者在透彻地理解数据挖掘基础的同时,还能了解更多重要的高级主题。 本书特色 ·包含大量的图表、......一起来看看 《数据挖掘导论》 这本书的介绍吧!

图片转BASE64编码
图片转BASE64编码

在线图片转Base64编码工具

随机密码生成器
随机密码生成器

多种字符组合密码

URL 编码/解码
URL 编码/解码

URL 编码/解码