动态规划不是一个具体的算法,而是一种思想。
这种思想具体的就是 从局部最优解采用一定策略推导出全局最优解,从子问题的答案中一步步推出整个问题的答案。
解决动态规划问题通常采用四个步骤:
- 问题拆解(找到问题的子问题)
- 状态定义(使当前状态就是当前问题的解)
- 写出递推方程(之前相邻子问题的答案推出当前状态的答案)
- 代码实现
通过几个题目去理解动态规划和这四个步骤。
- 三角形最小路径和问题
问题描述:
给定一个三角形,找出自顶向下的最小路径和.每一步只能移动到下一行中相邻的节点上:
[
[2]
[3,4]
[6,5,7]
[4,1,8,3]
]
自顶向下的最小路径和为 11 (即,2+3+5+1=11)
#说明如果你可以用O(n)的额外空间(n为三角形的总行数)来解决问题,那么你的算法会很加分.
#(这个是降低空寂复杂度的问题,实现算法之后在考虑改善算法,先不考虑他)
按照上面四步分析问题:
首先问题的拆解:
其实这个三角形可以看出来就是一个二维数组
这里的问题是求最小路径和,所有路径是由一个个元素(数字)组成的
所以问题就可以想象成:
每到达一个元素的路径是由当前元素加上当前元素的上一个或两个元素路径得到的
进而把问题拆解成到达每一个元素最小路径的子问题
状态定义:
状态定义就是要和问题求解的答案紧密联系到一起
这个问题有两个思路,一个是从上到下找路径,另一个从下到上找路径
首先我们看从上到下怎么想:
这里会有一个问题,看下面例子:
[
[2]
[3,4]
[6,5,1]
[4,1,8,3]
]
显然最短路径是 2 + 4 + 1 + 3 = 10
如果只考虑当前从2到3是最短,不选择4,当到下一层时
根据问题要求(每一步只能移动到下一行中相邻的节点上)就不能选择1,问题答案就错了。
还有一个问题:
每一层元素的起始元素路径只能选择[i-1][j]即上一层的起始元素
最后元素选[i-1][j-1],即上一层最后一个元素
但是中间元素就有[i-1][j],[i-1][j-1]两种选择
所以就不太好实现。我们用从下到上的思想。
从下到上的方式:
[
[2] [10] #我们可以看出[0][0]个元素就是我们的答案
[3,4] [9 8]
[6,5,1] [7 6 4] # 6可以选4和1,5可以选1和8 #记录当前所有元素
[4,1,8,3] 从最后一层向上递推 #到最后一层最短路径
]
我们定义的状态就是 最后一行元素到当前所有元素最小路径和。
递推方程:
状态定义好了,递推方程就好写了
第[i][j]个元素为当前状态 = 三角形第[i][j]个元素加上([i+1][j]与[i+1][j+1]中小的)
dp[i][j] = triangle[i][j]+min(dp[i+1][j],dp[i+1][j+1])
代码实现:
在文末.
- 数组中连续子数组的最大和
问题描述:
给定一个整数数组N,找到一个具有最大和的连续子数组
(子数组最少包含一个元素),返回其最大和。
例如:
输入:[1,-5,2,4,-1,3]
输出:8
因为连续子数组[2,4,-1,3]的和最大。
按照四步分析问题:
1.拆解问题
要找整个数组中最大的子数组和,就是去比较所有子数组谁
的和最大.
子数组可以看成一个区间,都会有一个起点和一个终点定义.
由一个起点到第 i 个点组成的子数组 换句话说就是:
以第i个数结尾的子数组,我们去求解所有以第i个数为结尾的
子数组的最大和.
例如:
[1,2,11,2,3]
^
|
第i个数是3,则以3结尾的最大和就是前第i-1为结尾,
此处是以2结尾的子数组[1,2,-1,2]的最大和加上第i个数,也就是3.
[1,2,11,2,3]
^
|
第i个数是2,则以2结尾的最大和就是前第i-1为结尾,
此处是以1结尾的子数组[1]的最大和加上第i个数,也就是2.
这样我们就可以把问题从i 拆解成 i-1 即:
从第一个起始元素开始推出第 i 个……直到整个数组结束。
如果i-1结尾子数组的最大和为负数,则i结尾字数组最大和就是i.
2.状态定义
定义状态应该紧密联系我们最终想要的答案,这个问题第i个状态
dp[i] 就是以i为结尾的所有子数组最大和.
3.递推方程
dp[i] = max(dp[i-1],0) + array[i]
4 代码实现
文末
问题描述:
一共有 n 阶楼梯,每一次你可以爬一个台阶或两个
你有多少种不同的方法可以爬到楼顶?
(其中,给定的 n 是一个整数)
示例:
输入:2
输出:2
因为两阶台阶有两种方式爬到楼顶
1. 爬一阶再爬一阶
2. 直接爬两阶
按照四步分析问题:
1.拆解问题
一共n阶台阶,有两种方式爬,所以我们到达第n阶的时候
不是从第n-2爬上来,就是从第n-1阶爬上来,所以我们把
问题看成到达第 n-2 阶时所有方式加上到达第n-1阶时
所有方式就是到达第n阶的所有方式.
2.状态定义
第 i 个状态dp[i]就是到达第 i 阶楼梯时存在的所有爬楼
梯方法种类数量.
3.递推方程
dp[i] = dp[i-1] + dp[i-2]
4.代码实现
文末
'''
三角形最小路径和(动态规划问题)
'''
class Solution(object):
def minimumTotal(self, triangle):###原始算法
if not triangle:
return 0
n=len(triangle)
dp=[[None]*n]*n
array = triangle[-1]
for i in range(0,n):
dp[n-1][i] = array[i]
for i in range(n-2,-1,-1):
for j in range(0,len(triangle[i])):
dp[i][j] = min(dp[i+1][j],dp[i+1][j+1])+triangle[i][j]
return dp[0][0]
def minimumTotal_test(self, triangle):#####改进算法
if not triangle:
return 0
h = len(triangle) # 金字塔高度
lay = triangle[-1] # 取到金子塔最下面一层:是一个列表
i = h - 2 # 取到金字塔层数的倒数第二层: 是一个数字
while i >= 0:
j = 0 # j:遍历金字塔每一层中元素指针
while j <= i: # 本来行数和每层的元素数量是一致的,但索引从0开始所以减1,底下j有j+1所以再次减1,故直接是j<=i
lay[j] = min(lay[j], lay[j + 1]) + triangle[i][j] # 对上一层的一个值找其正下方和左下方的最小值,赋到其上
j += 1
i -= 1
return lay[0] # i为0时取到的是最上面一层,最上面一层只有一个值
if __name__ == '__main__':
s=Solution()
print(s.minimumTotal_test([[2],[2,3]]))
'''
输入一个整形数组,可能有正有负,求数组中连续子数组的最大和
要求时间复杂度为O(n)
'''
class Solution:
def multiply(self , A):
n=len(A)
dp=[None]*n
dp[0]=A[0] #初始化,数组起始元素,最大和是元素本身
result=0
for i in range(1,n):
dp[i]=max(dp[i-1],0)+A[i]
result = max(result,dp[i])
return result
if __name__ == '__main__':
s=Solution()
#输入以逗号分隔的数组元素
str_in= input()
a = [int(n) for n in str_in.split(',')] #将输入转变成数组
print(s.multiply(a))
'''
爬楼梯问题
'''
class Solution:
def Stairs(self , n):
dp=[None]*n
dp[0] = 1 #初始化第一个台阶
dp[1] = 2
for i in range(2,n):
dp[i] = dp[i-1] + dp[i-2]
return dp[n-1]
if __name__ == '__main__':
s=Solution()
print(s.Stairs(4))