动态规划问题分析

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/u012494820/article/details/82177203

本博客讨论的问题主要参考AlgoExpert中dynamic programming的question list,求解方法参考Geeksforgeeks网站,代码位于我的GitHub。
- max subset sum no adjacent
- number of ways to make change
- edit/levenshtein distance
- ugly number
- longest common subsequence
- longest increasing subsequence
- min number of jumps
- container with most water
- max profit with K transaction 1, 2, 3

需要求解:
- minimum number of coins
- Palindrome partitioning min cut 1, 2

问题特点分析

动态规划方法求解的问题一般具有两个特点:(1)最优的子结构;(2)重叠的子问题。
最优的子结构指如果对原问题的选择范围进行扩充,则新问题的最优解位于{原问题最优解,原问题+扩充范围得到的最优解}。
重叠的子问题指当问题的范围逐渐扩大时,后面的求解会多次求解前面已经求解过的问题。
考虑到动态规划问题的这两个特点,我们求解问题时可以先分析问题,用递归的形式得到问题的解,然后考虑对问题进行优化(例如存储每一个小问题的计算结果,避免后面的重复计算)。求解动态规划问题的核心为寻找状态转移方程,建立起原问题与子问题之间的联系。
求解fibonacci数列为例,根据问题描述,我们可以显示地得到状态转移方程f(i)=f(i-1) + f(i-2), i>=2。据此,有两种求解思路:

  • 递归求解。根据状态转移方程求解,需要注意处理好终止条件。这种解法思路清晰,但多层递归导致计算复杂度高。
   def fib(n):
    # base case
    if n == 0 or n == 1:
        return n
    return fib(n-1) + fib(n-2)
  • 存储计算结果,逐个推导。如果我们分析整体计算过程,存储必要的子结果,可以使得计算更高效。
     
    def fib_tabulation(n, table):
    table[0] = 0
    table[1] = 1
    if n <= 1:
    return table[n]
    for i in range(2, n + 1):
    table[i] = table[i - 1] + table[i - 2]
    return table[n]

实例分析1: edit/levenshtein distance

我们希望使用3种类操作(remove,insert,replace)将str1转化为str2。采用递归的方法,对比str1与str2末尾的字母,相同,不进行操作;不同,选择一种操作执行。

 
def edit_distance(str1, str2):
if len(str1) == 0:
return len(str2)
if len(str2) == 0:
return len(str1)
if str1[-1] == str2[-1]:
return edit_distance(str1[:-1], str2[:-1])
else:
d_insert = edit_distance(str1, str2[:-1])
d_remove = edit_distance(str1[:-1], str2)
d_replace = edit_distance(str1[:-1], str2[:-1])
return 1 + min(d_insert, d_remove, d_replace)

当然,我们可以采用数组,存储只考虑str1和str2前面一部分时,它俩之间的的edit distance。从上到下、从左到右地构建数组。注意,在Python中创建m行n列空list的方法为:
 
arr = [[None] * n for i in range(m)]

 
def edit_distance_optim(str1, str2):
m = len(str1)
n = len(str2)
arr = [[None] * n for i in range(m)]
for irow in range(m):
for jcol in range(n):
if irow == 0:
arr[irow][jcol] = jcol
elif jcol == 0:
arr[irow][jcol] = irow
elif str1[irow] == str2[jcol]:
arr[irow][jcol] = arr[irow-1][jcol-1]
else:
d_insert = arr[irow][jcol-1]
d_remove = arr[irow-1][jcol]
d_replace = arr[irow-1][jcol-1]
value = min(d_insert, d_remove, d_replace)
arr[irow][jcol] = value + 1
return arr, arr[m-1][n-1]

实例分析2:longest increasing subsequence

设输入矩阵为arr[0, 1, …, n-1],创建矩阵L[i]表示以索引i处的元素arr[i]作为LIS序列的最后一个元素能得到的LIS序列的长度。则状态转移方程可以递归的写为:
L(i) = 1 + max( L(j) ) ,0 < j < i 且arr[j] < arr[i]; 或者 L(i) = 1, if 上述j不存在.
矩阵L[i]可以存储计算过的子问题,提高效率

 
def lis_optim(arr):
n = len(arr)
# lis_doc
lis_doc = [1] * n
for i in range(n):
for j in range(i):
if arr[j] < arr[i] and (lis_doc[j]+1 > lis_doc[i]):
lis_doc[i] = lis_doc[j] + 1
maximum = 1
for i in range(n):
maximum = max(lis_doc[i], maximum)
return maximum

需要阅读的补充材料理解动态规划分析动态规划题目
简要地说,动态规划的本质是对于状态的定义和状态转移方程的定义。对于给定的问题,我们需要寻找符合“最优子结构”的状态和状态转移方程定义,然后使用记忆化地求解递推式的方式解决。

猜你喜欢

转载自blog.csdn.net/u012494820/article/details/82177203
今日推荐