动态规划精简复习

动态规划

Dynamic Programming

1.递推求解

由于递归问题的解可能会重复计算多次,产生不必要的计算,所以可以将计算过的子问题的解保存起来,就是递推。

典型的问题包括斐波那契数列,N阶上楼梯问题

N阶上楼梯问题

每次上楼梯可以上1格或者两格,所以上到第n格总共的方式数目 d p [ n ] = d p [ n − 1 ] + d p [ n − 2 ] dp[n]=dp[n-1]+dp[n-2] dp[n]=dp[n1]+dp[n2]

2.最大连续子序列和

给定一个序列{ A 1 A_{1} A1, A 2 A_{2} A2, A 3 A_{3} A3, A 4 A_{4} A4 A n A_{n} An},找到一个连续的子序列(下标连续),使得子序列的和最大。

dp[i]为以a[i]作为结尾的连续序列最大和。

dp[i]=max(a[i],dp[i-1]+a[i])

拓展:最大连续子矩阵和(二维情况)

解决办法:假设最大矩阵在第 i 行到第 j 行,则会出现两种情况:

  • i == j 问题转化为一维的问题,求第 i 行的最大连续子序列和即可。
  • i != j 将第i行到第j行的元素加起来,得到一个一维数组,求最大连续子序列和就是最大子矩阵的和。(每行元素的累加结果可以通过构造一个辅助数组得到)

3.最长递增子序列

给定一个序列{ A 1 A_{1} A1, A 2 A_{2} A2, A 3 A_{3} A3, A 4 A_{4} A4 A n A_{n} An},找到一个最长的子序列(下标可以不连续),对子序列中的任意下标 x < y x<y x<y A x A_{x} Ax< A y A_{y} Ay.

dp[i]表示以a[i]为末尾的最长递增子序列的长度。

dp[1]=1;

dp[i]=max(1,dp[j]+1) j<i && a[j]<a[i]

由于下标可以不连续,所以要找到前面那个最长递增子序列长度。所以在循环遍历第i个位置的前面的子序列时,要让dp[i]=max(dp[i],dp[j]+1),而不是dp[i]=max(1,dp[j]+1).后面那个表达式其实只判断了前一个位置的子序列长度。

拓展:最大上升子序列和(下标可以不连续)

给定一个序列,找到一个上升子序列,使得子序列和最大。求这个和。

dp[i]表示以a[i]为结尾的最大上升子序列和。

dp[1]=a[i];

dp[i]=max(a[i],dp[j]+a[i]) j<i && a[j]<a[i].如果a[i]前面的元素都比它大,则dp[i]=a[i].

4.最长公共子序列

给定两个字符串 S 1 S_{1} S1 S 2 S_{2} S2,求一个最长公共子串,他同时为 S 1 和 S_{1}和 S1 S 2 S_{2} S2的子串,且要求它长度最长,并确定这个长度。

设置二维数组dp[][]dp[i][j]表示以 S 1 [ i ] S_{1}[i] S1[i]为末尾和 S 2 [ j ] S_{2}[j] S2[j]为末尾的最长公共子序列的长度。最长公共子序列的长度是dp[n][m]的值。

根据 S 1 [ i ] S_{1}[i] S1[i] S 2 [ j ] S_{2}[j] S2[j]的关系,分为两种情况:

  • S 1 [ i ] = = S 2 [ j ] S_{1}[i]==S_{2}[j] S1[i]==S2[j]dp[i][j]=dp[i-1][j-1]+1
  • S 1 [ i ] ! = S 2 [ j ] S_{1}[i]!=S_{2}[j] S1[i]!=S2[j]dp[i][j]=max(dp[i-1][j],dp[i][j-1])

边界情况:

  • dp[i][0]=0
  • dp[0][j]=0

5.背包问题

1. 0-1背包

n件物品,每个物品价值为w[i],价值为v[i],现在有容量为m的背包,选择物品使得装入背包价值最大。求最大价值。

设置一个二维数组dp[i][j],表示将前i个物品装入容量为j的背包时,能获得的最大价值。dp[n][m]为原问题的解。

对于第i个物品,只有装与不装两种状态:

  • 装入背包,dp[i][j]=dp[i-1][j-w[i]]+v[i]
  • 不装入背包,dp[i][j]=dp[i-1][j]

dp[i][j]=max(dp[i-1][j-w[i]]+v[i],dp[i-1][j])

注意:

  • 转移时要注意判断j-w[i]的值是不是负数,如果是负数不能转移。
  • 边界情况dp[0][j]=0 dp[i][0]=0

转移方程优化:

由于上面的状态转移方程中,dp[i][j]的值仅与dp[i-1][j-w[i]]dp[i-1][j]的值有关,也就是说只和二维数组的上一行有关,所以状态转移方程可以优化成一维数组:

dp[j]=max(dp[j-w[i]]+v[i],dp[j])

这个方程只是在存储上进行了优化,实际上算法的时间复杂度并没有降低,我们还是要写两重循环进行遍历。这个方程可以理解为对于不同的物品加入背包,判断当前容量为j时这个物品的加入是否能使得当前dp[j]的值增加,即价值增大,如果增大就更新,否则不更新。

遍历顺序

for i = 0-n:

	for j = m-0:

		if j>=w[i] dp[j]=max(dp[j],dp[j-w[i]])

print(dp[m])

2.完全背包

将01背包进行扩展,每种物品可以拿多个,就是完全背包问题。

和01背包一样,设置二维数组dp[i][j],表示将前i个物品装入容量为j的背包时,能获得的最大价值。两种状态,取或者不取第i件物品:

  • 不取第i件物品,dp[i][j]=dp[i-1][j]
  • 取第i件物品,由于可以取多次,所以状态方程转移到dp[i][j-w[i]],而不是dp[i-1][j-w[i]]。即dp[i][j]=dp[i][j-w[i]]+v[i]

转移方程优化:

dp[j]=max(dp[j],dp[j-w[i]]+v[i])

可以发现状态转移方程和01背包一模一样,为了保证转移正确,要保证每次更新确定状态dp[j]时,dp[j-w[i]]已经完成了本次更新修改。所以要正序遍历j,而01背包是逆序遍历j。
遍历顺序

for i = 0-n:

	for j = 0-m:

		if j>=w[i] dp[j]=max(dp[j],dp[j-w[i]])

print(dp[m])

3.多重背包

多重背包问题介于01背包和完全背包之间,每件物品最多取k件,求装入背包物品最大价值。

我们可以把多重背包问题转换为01背包问题,将每种物品都视为k中价值和重量都不同的物品,时间复杂度为 O ( m ∑ k i ) O(m\sum k_{i}) O(mki).降低每种物品的数量可以降低其复杂度。可以采用一种更有技巧的拆分:

将原数量为k的物品分为若干组,每组包含 2 0 , 2 1 . . . 2^{0},2^{1}... 20,21...,类似于二进制的拆分,将物品数量降低,同时通过原物品与新物品的不同组合,可以得到0-k之间的任意件物品的价值总和,所以对这些新物品做01背包,可以得到多重背包的解。优化后时间复杂度为 O ( m ∑ l o g 2 ( k i ) O(m\sum log_{2}(k_{i}) O(mlog2(ki).

6.其它问题

动态规划问题灵活多变,这里只是介绍了几种经典且常见的动态规划问题,更多问题要结合实际定义状态数组,写出状态转移方程。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值