动态规划是算法中的难点与重点,面试的时候应该也会经常遇到。动态规划是运筹学的一个分支,是求解决策过程最优化的教学问题,其处理对象是多阶段决策问题。这种问题一般可以分解成为若干个相互联系的阶段,在每一个阶段都要做出决策,形成一个决策序列,该决策序列也成为一个策略。对于每一个决策序列,可以在满足问题的约束条件下用一个数值函数(即目标函数)衡量该策略的优劣。多阶段决策问题的最优化目标是获取导致问题最优值的最优决策序列,即得到最优解。
最优性原理
假设为了解决某一多阶段决策过程中的优化问题,需要依次作出n个决策,如果这个决策序列是最优的,对于任何一个整数k,
,无论前面k个决策
是怎样的,以后的最优决策只取决于由前面决策所确定的当前状态,即以后的决策序列
也是最优的。
最优性原理体现为问题的最优子结构特性,最优子结构特性是动态规划求解问题的必要条件。
个人觉得递推算法的思想是动态规划算法的第一步。
动态规划实施步骤
1. 把所求最优化问题分成若干个阶段,找出最优解的性质,并刻划其结构特性。
2. 将问题各个阶段时所处不同的状态表示出来,确定各个阶段状态之间的递推关系,并确定初始条件。
3. 应用递推求解最优值
4. 根据计算最优值时所得到的信息,构造最优解,构造最优解就是具体求出最优决策序列。
示例
1. 插入乘号问题,n在一个由n个数字组成的数字串中插入r个乘号(1≤r<n),将它分成r+1个整数,找出一种乘号的插入方法,使得这r+1个整数的乘积最大。例如:在数字串847313926中插入5个乘号,分为6个整数相乘,使乘积最大的最优解为:
该最优解包含了以下子问题的最优解:
1) 在84731中插入2个乘号使得乘积最大为:8*2*731;
2) 在7313中插入1个乘号使得乘积最大为:731*3;
3) 在3926中插入2个乘号使乘积最大为:3*92*6;
4) 在4731392中插入3个乘号使得乘积最大为:4*731*3*92
第一步:首先建立递推关系,设表示在前i位数中插入k个乘号所得乘积的最大值,
表示从第i个数字到第j个数字所组成的j-i-1(i<=j)位整数值。
一般地,为了求取,考察数字串的前i个数字,设前j(k<=j<i)个数字中已经插入k-1个乘号的基础上,在第j个数字后插入第k个乘号,显然此时的最大乘积为
。于是可以得到以下递推关系式:
前j个数字没有插入乘号时的值显然为前j个数字组成的整数,因而得到边界值:,其中
。
第二步:递推设计最优值,以下为实现上述问题的python代码:
temp = str(input()) #输入数字串
r = int(input()) #输入要插入多少个乘号
#对数字串temp进行分离
number = []
length = len(temp)
for i in range(length):
number.append(int(temp[i]))
d = 0
f = [[0 for i in range(r+1)] for j in range(length+1)]
#初始化边界条件
for j in range(1,length+1):
d = d*10+number[j-1]
f[j][0] = d
for k in range(1,r+1):
for i in range(k+1,length+1):
for j in range(k,i):
d = 0
for u in range(j+1,i+1):
d = d*10+number[u-1]
if(f[i][k]<f[j][k-1]*d):
f[i][k] = f[j][k-1]*d
print("最优解为:",f[length][r])
2. 最长非降子序列,由n个正整数组成的序列,从该序列中删除若干个整数,使剩下的整数组成非降子序列,求最长的非降子序列。例如,由12个正整数组成的序列为:
请在序列中删除若干项,使得剩下的项为非降(即后面的项不小于前面的项)序列,非降序列最多为多少项:
第一步,建立递推关系,设序列的各项为a[1],a[2],...,a[n],对每个整数操作作为一个阶段,共为n个阶段。设置b数组,b[i]表示序列的第i个数(包含第i个数)到第n个数中的最长非降子序列的长度,i=1,2,...,n。对所有的i>j,比较当a[j]>=a[i]时b[j]的最大值,显然b[i]为这一项最大值加1,表示加上a[i]本省这一项。因而有递推关系:,其中
。边界条件b[n]=1。
第二步:递推计算最优值,以下是上述问题的python代码实现:
number = list(map(int,input().split(',')))
length = len(number)
b = [0 for i in range(length)]
b[length-1] = 1
for i in range(length-1,-1,-1):
maxnumber = 0
for j in range(i+1,length):
if(number[i]<=number[j] and b[j]>maxnumber):
maxnumber = b[j]
b[i] = maxnumber+1
print(max(b))
3. 矩阵中的最大路径,在一个给定的n行m列矩阵中,从矩阵的左上角走到右下角,路径中每一步能往右、往下走到相邻格子,不能斜着走,也不能走出矩阵。试着在所有路径中搜索路径上各数和最大的最大路径。例如,所示10x8矩阵中,如何确定最大路径?
设第(i,j)表示第i行第j列格,数组a(i,j)存储该格(i,j)中的数字;数组b(i,j)为(i,j)至矩阵右下角(n,m)路径的最大数字和;数组c(i,j)存储(i,j)下步向标:向下为“D”,向右为“R”;数组d(i,j)从格(i,j)至右下角(n,m)不同最大路径的条数。显然,最优路径的数字和为b(1,1)。
第一步,建立递推关系,b(i,j)与c(i,j)(i=n,...,2,1;j=m,...,2,1)的值由整数b(i+1,j)与整数b(i,j+1)的大小决定:
这样反推所得到的b(1,1)即表示所求的最大路径数之和。我们可以看到,并非直接计算从左上角第一个格子到右下角最后一个格子的路径,而是从右下角最后一个格子反向推导到左上角第一个格子的路径。
我们看以下数组d的递推关系:
数组d(i,j)从格(i,j)至右下角(n,m)不同最大路径的条数。
第二步,确定边界条件,最后一行和最后一列只有一个出口,最后一行只能向右边走,最后一列只能向下面走,现在由b[n][m]=a[n][m]开始:
产生最优路径:路径左上角(i=1,j=1)开始,至右下角(i=n,j=m)结束,中间各点通过循环while(i+j<m+n)实现。
第三步:递推计算最优值,以下是上述问题的python代码实现:
a = [[2,3,4,2,1,1,4,5],
[2,1,3,2,5,2,3,1],
[5,4,5,5,1,1,3,5],
[4,1,4,4,2,2,1,3],
[5,4,4,2,4,5,5,4],
[5,4,3,4,1,1,1,2],
[3,4,3,3,1,4,1,2],
[5,2,3,5,4,3,2,2],
[3,3,4,2,2,5,4,5],
[5,5,4,2,4,5,3,4]]
n = len(a) #行
m = len(a[0]) #列
b = [[0 for i in range(m)] for j in range(n)]
c = [[0 for i in range(m)] for j in range(n)]
d = [[0 for i in range(m)] for j in range(n)]
#确定边界条件
b[n-1][m-1] = a[n-1][m-1]
for j in range(m-2,-1,-1):
b[n-1][j] = a[n-1][j]+b[n-1][j+1]
c[n-1][j] = "R"
d[n-1][j] = 1
for i in range(n-2,-1,-1):
b[i][m-1] = a[i][m-1]+b[i+1][m-1]
c[i][m-1] = "D"
d[i][m-1] = 1
#建立递推关系
for i in range(n-2,-1,-1):
for j in range(m-2,-1,-1):
if(b[i+1][j]>b[i][j+1]):
b[i][j] = a[i][j]+b[i+1][j]
c[i][j] = "D"
d[i][j] = d[i+1][j]
if (b[i + 1][j] == b[i][j + 1]):
b[i][j] = a[i][j] + b[i + 1][j]
c[i][j] = "D"
d[i][j] = d[i + 1][j] + d[i][j+1]
if (b[i + 1][j] < b[i][j + 1]):
b[i][j] = a[i][j] + b[i][j+1]
c[i][j] = "R"
d[i][j] = d[i][j + 1]
print(b[0][0])
总结:动态规划根据不同阶段之间的状态转移,通过应用递推求得问题的最优值。这里,注意不能把动态规划与递推两种算法相混淆,不要把递推当成是动态规划,也不要把动态规划当成递推。多刷题有利于熟悉算法。