动态框架方法论

动态规划

20201229

14:06

 

目录

-

- 经典三概念与解决思想

- 经典的解决思想

- 动态规划的方法论

- 案例:找零

- 求解框架

- REF

 

 

经典三概念与解决思想

动态规划是什么?动态规划有三个特征。

  • 重复子问题,这个特点很好理解,递归树是一个非常好方法,它的一个功能就是能直观地感受到重复子问题
  • 最优子结构,当前问题可以由子问题的solution解决,这个特性,递归地使得当前问题和子问题具备相同的结构
  • 状态转移方程,这也是最难的一步,既然当前问题可以由子问题解决,那么转移公式是?

经典的解决思想

  • 自顶向下,逐步求解
  • 自底向上,反向求精

这是计算机世界一种经典的思想,在动态规划、递归、回溯中,自顶向下的方法通常是非常直接的暴力解法——这非常有用,是一切优化的开始。

动态规划的方法论

解决这些问题需要找到四个要素:

  • base,求解停止的基础条件是?到哪种状态可以直接返回。
  • state,状态,这是非常关键的,它指的是可以代表当前问题的一些状态量的集合,对应到递归树中,这个状态就是递归树中的一个节点——这非常重要。
  • select,选择,改变状态的行为
  • dp function,dp动态规划函数,函数一般返回某个状态的solution,这个也是最难定义的暴力解决方案

以找零钱问题为例,请参考REF中原题

  • base,当amount=0的时候,表示不用凑硬币了,直接return 0
  • state,状态,这个问题的关键状态量只有一个,那就是当前问题的amount——这个目标金额,最少需要多少枚硬币可以凑齐
  • select,改变目标金额的唯一行为是选择某个硬币
  • dp函数,根据以上dp函数可以定义为,c(最少需要的硬币数) = dp(目标金额)

案例:找零

根据上述分析找零问题的暴力求解伪代码框架是:

    1 def dp(n):

    2     for coin in coins:

    3         res = min(res, dp(n-coin)+1)

    4     return res

详细解法,可以参考原文,见ref。

上述框架,加上基值条件,完整的代码如下。

    1 def dp(n):

    2     if n == 0: return 0

    3     if n < 0: return -1

    4     res = float('INF')

    5     for coin in coins:

    6         t = dp(n-coin)

    7         if t == -1: continue

    8         res = min(res, t+1)

    9     return res if res != float('INF') else -1

   10 # 备忘录

   11 memo = {}

   12 def dp(n):

   13     # memo

   14     if n in memo: return memo[n]

   15     # base

   16     if n == 0: return 0

   17     if n < 0: return -1

   18     # init res

   19     res = float('INF')

   20     # chose coin

   21     for coin in coins:

   22         t = dp(n-coin)

   23         # invalid

   24         if t == -1: continue

   25         res = min(res, t+1)

   26     memo[n] = res if res != float('INF') else -1

   27     return memo[n]

   28 # 迭代

   29 memo = [0]

   30

   31 for n in range(1, amount+1):

   32     memo.append(amount+1)

   33     for coin in coins:

   34         # 检查越界

   35         if n - coin >= 0:

   36             memo[n] = min(memo[n-coin]+1, memo[n])

   37 return memo[n] if memo[n] != (amount+1) else -1

  • 去除重复子问题的最直接的方式就是使用备忘录记录状态
  • 完成了备忘录方法,那么最后的问题就是,如何自底向上计算出备忘录(所有状态对应的解法的数组)

代码如上。

求解框架

该框架有点令人费解,但是仔细对照找零问题,其中核心问题就是理解“状态”——状态备忘录,记录的是当前状态的solution。

    1 # 初始化 base case

    2 dp[0][0][...] = base

    3 # 进行状态转移

    4 for 状态1 in 状态1的所有取值:

    5     for 状态2 in 状态2的所有取值:

    6         for ...

    7             dp[状态1][状态2][...] = 求最值(选择1,选择2...)

 

REF

 

  • https://labuladong.gitbook.io/algo/di-ling-zhang-bi-du-xi-lie/dong-tai-gui-hua-xiang-jie-jin-jie

经典的入门问题是斐波那契数列数列,以及找零钱问题。解法太过常见,这次来谈一些正确的思路。

  • https://leetcode.com/problems/coin-change/
  • https://leetcode.com/problems/fibonacci-number/

 

你可能感兴趣的:(算法编程)