动态规划与递归的关系
- 递归与动态规划是两个刚好相反但是解题思路相同的过程。
- 两个方法都是通过把一个大的问题化小,小到可以一步解决,递归是采用从顶层 层层递归,知道递归出口之后得到结果。
- 递归的痛点是重复计算重复子问题
以斐波那契数列为例,用递归会重复计算左右分支的结点,时间复杂度2^n.
动态规划的优势就体现出来了,将计算过的值都记忆了。
递归协助解决动态规划
- 写出大规模问题和子问题的递推关系式
- 找到递归出口
- 设计动态规划数组
- 编写代码
示例:相加和为9
给一个int型数组arr,选择若干数字,若相加能得到数s,则返回True,否则返回False。
找递推关系
接下来讨论是否选择arr[4],只有选和不选两种结果。
subset(arr[i], s)
subset(arr[5], 9) = subset(arr[4], 7) or subset(arr[4], 9)
递归出口
subset(arr[i], s)
- 处理过程中发现 s == 0,说明已经可以返回True,接下来不需要找了
if s == 0:
return True
- 当数组找啊找只剩下最后一个数字arr[0]时,直接比较arr[0]是否与最终要凑的s相等,若相等返回True。
if i == 0:
return arr[0] == s
- 当找到的数比s大,比如选了34就不可能凑到9,那么这个时候只有一种选择,就是不选它,相当于直接将其从数组移除
if arr[i] > s:
return subset(arr[i-1], s)
(递归代码已经出来了)
def rec_subset(arr, i, s):
if s == 0:
return True
elif i == 0:
return arr[0] == s
elif arr[i] > s:
return rec_subset(arr, i-1, s)
else:
A = rec_subset(arr, i-1, s - arr[i])
B = rec_subset(arr, i-1, s)
return A or B
设计动态规划数组
第一横排对应第一个出口
第一个竖排对应第二个出口
动态规划程序
import numpy as np
def dp_subset(arr, s):
subset = np.zeros((len(arr), s+1), dtype=bool)
subset[:, 0] = True # 第一横排
subset[0, :] = False # 第一列
subset[0, arr[0]] = True # 左上角为True
for i in range(1, len(arr)):
for s in range(1, s+1):
if arr[i] > s:
subset[i, s] = subset[i-1, s]
else:
A = subset[i-1, s - arr[i]]
B = subset[i-1, s]
subset[i, s] = A or B
r, c = subset.shape # 返回这个numpy的行列数
return subset[r-1, c-1] # 返回右下角的值即结果