最近几天一直在看有关动态规划的算法,整理了一些常见案例,主要是求最长公共子序列,最长公共子串,最长递增子序列,最长回文子串,硬币的组合数,硬币的最少组合方法,最小编辑距离,背包问题(01背包,完全背包,多重背包)等方面的经典案例求解。
这些案例大部分都是用python实现的动态规划算法。
Q:给定两个序列,找出在两个序列中同时出现的最长子序列的长度。一个子序列是出现在相对顺序的序列,但不一定是连续的。
分析:
def findLongest(self, A, n, B, m):
#新建一个m行n列的矩阵
matrix = [0] * m * n
#1、矩阵的第一行,即matrix[0][i],代表str1[0]与str2[0...n]的最长公共子串.
# str2[0]只有一个字符,所以matrix[i][0]最大为1
for i in range(n):
if A[i] == B[0]:
for j in range(i,n):
matrix[j] = 1
#2、矩阵的第一列,matrix[i][0]最大为1
for i in range(m):
if B[i] == A[0]:
for j in range(i,m):
matrix[j*n] = 1
#3、其他位置,matrix[i][j]有三种情况,matrix[m][n]即为所求的最长公共子序列长度
for i in range(1,m):
for j in range(1,n):
if B[i] == A[j]:
matrix[i*n+j] = max(matrix[(i-1)*n+j-1]+1,matrix[(i-1)*n+j],matrix[i*n+j-1])
else:
matrix[i*n+j] = max(matrix[(i-1)*n+j],matrix[i*n+j-1])
return matrix[m*n-1]
Q:给定两个序列,找出在两个序列中同时出现的最长子序列的长度。子串的意思是要求为连续的子序列
分析:
矩阵的第一行,即matrix[0][i],代表str1[0]与str2[0...n]的最长公共子串.
与案例一中的前两步相同,只是最后一步不同。
代码:
def findLongest(self, A, n, B, m):
#新建一个m行n列的矩阵
matrix = [0] * m * n
#1、矩阵的第一行,即matrix[0][i],代表str1[0]与str2[0...n]的最长公共子串.
# str2[0]只有一个字符,所以matrix[i][0]最大为1
for i in range(n):
if A[i] == B[0]:
matrix[i] = 1
#2、矩阵的第一列,matrix[i][0]最大为1
for i in range(m):
if B[i] == A[0]:
matrix[i*n] = 1
#3、其他位置
max = 0
for i in range(1,m):
for j in range(1,n):
if B[i] == A[j]:
matrix[i*n+j] = matrix[(i-1)*n+j-1]+1
if max
Q:给定一个序列,找到最长子序列的长度,使得子序列中的所有元素被排序的顺序增加。比如arr = [2,1,5,3,6,4,8,9,7], 最长递增子序列为[1,3,4,8,9],所以返回这个子序列的长度5。给定数组arr,返回数组arr,返回arr的最长递增子序列长度。比如arr =[2,1,5,3,6,4,8,9,7],最长递增子序列为[1,3,4,8,9],所以返回这个子序列的长度5
分析:
dp[i]表示在必须以arr[i]结尾的情况下,arr[0 ... i]中的最大递增子序列长度,
dp[i] = max{ dp[j]+1 (0<=j
代码:
def findLongest(self, A, n):
dp = [0] * n
dp[0] = 1
for i in range(1, len(A)):
l = [1]
for j in range(0, i):
if A[i] > A[j]:
l.append(dp[j] + 1)
dp[i] = max(l)
return max(dp)
Q:给一个字符串,找出它的最长的回文子序列LPS的长度。例如,如果给定的序列是“BBABCBCAB”,则输出应该是7,“BABCBAB”是在它的最长回文子序列。
分析:
dp[i][j] = 1表示字符串s从i到j是回文串 dp[i][j] = 0表示字符串s从i到j不是回文串
如果dp[i][j] = 1, 那么dp[i+1][j-1] = 1
代码:
def manacher(self,s):
#建立一个二维数组
maxlen = 0
start = 0
dp = [[0 for i in range(len(s))] for i in range(len(s))]
for i in range(len(s)):
dp[i][i] = 1
if i+1maxlen:
start = j
maxlen = i
if maxlen>=2:
return s[start:start+maxlen]
return None
Q:假设有 1 元,3 元,5 元的硬币若干(无限),现在需要凑出 11 元,问如何组合才能使硬币的数量最少?
分析:
我们先假设一个函数 d(i) 来表示需要凑出 i 的总价值需要的最少硬币数量。
接着就不再举例了,我们来分析一下。可以看出,除了第 1 步这个看似基本的公理外,其他往后的结果都是建立在它之前得到的某一步的最优解上,加上一个硬币得到。得出:
d(i) = d(j)+1
这里j凑出 i 元,就在凑出 j 的结果上再加上某一个硬币就行了。那这里我们加上的是哪个硬币呢。嗯,其实很简单,把每个硬币试一下就行了:
我们分别计算出d(i - 1) + 1,d(i - 3) + 1,d(i - 1) + 1的值,取其中的最小值,即为最优解,也就是d(i)。
最后公式
d(i) = min( d(i - 1) + 1,d(i - 3) + 1,d(i - 5) + 1 )
代码:
def findLeast(self, n):
# write code here
l = [0,1,2,1,2,1]
for i in range(6,n+1):
l.append(min(l[i-1]+1,l[i-3]+1,l[i-5]+1))
return l[n]
Q:有数量不限的硬币,币值为25分、10分、5分和1分,请编写代码计算n分有几种表示法。给定一个int n,请返回n分有几种表示法。保证n小于等于1000,为了防止溢出,请将答案Mod 1000000007。
分析:
代码:
def coinsWays(self, n):
coins = [1,5,10,25]
dp = [[0 for i in range(n+1)] for i in range(5)]
for i in range(5):
dp[i][0] = 1
for i in range(1,5):
for j in range(1,n+1):
for k in range(j/coins[i-1]+1):
dp[i][j] += dp[i-1][j-k*coins[i-1]]
return dp[4][n]
Q:给定一个长度为m和n的两个字符串,设有以下几种操作:替换(R),插入(I),删除(D)且都是相同代价的操作。寻找到转化一个字符串插入到另一个需要修改的最小(操作)数量。
分析:
代码:
def editDist(self,s1,s2):
#思路:
#dp[i][j] 表示长度为i的字符串A替换到长度为j的字符串B所付出的代价
len1 = len(s1)
len2 = len(s2)
dp = [[0 for i in range(len2+1)]for i in range(len1+1)]
for i in range(len1+1):
dp[i][0] = i
for i in range(len2+1):
dp[0][i] = i
for i in range(1,len1+1):
for j in range(1,len2+1):
#如果当前两个字符串指针所指向的字符相等时,
if s1[i-1]==s2[j-1]:
dp[i][j] = dp[i-1][j-1]
else:
dp[i][j] = min(dp[i-1][j],dp[i][j-1],dp[i-1][j-1])+1
return dp[len1][len2]
这里只写出了c++的写法
首先分别解释一下三种背包的含义:
关于01背包:
为什么叫01背包,因为装进去就是1,不装进去就是0,所以针对每个物品就有两种状态,装?不装?所以这个背包只要有足够大的空间,这个物品都是有可能被装进去的。
所以有状态转移方程
dp[i][m] = max(dp[i-1][m],dp[i-1][m-weight[i]+value[i]])
for (i = 1; i <= n; i++) #从1开始是因为这涉及到dp[i-1][j],从0开始会越界
for (m = v; j >= weight[i]; j--)//在这里,背包放入物品后,容量不断的减少,直到再也放不进了
dp[i][m] = max(dp[i-1][m],dp[i-1][m-weight[i]+value[i]])
仔细分析就会发现,这种二维数组开销很大,因此有了下面的滚动数组,说白了只是把所有的物品都跑一遍,然后到最后一个物品的时候输出答案,那么过程值只是计算的时候用一次,没必要存下来,所以用一个数组去滚动存储,然后用后一个状态的值去覆盖前一个状态。
for(int i=1; i<=n; i++)//对每个数判断,可反
{
for(int j=m; j>=weight[i]; j--)//这里这个循环定死,不能反,反了就是完全背包
{
dp[j]=max(dp[j],dp[j-weight[i]]+value[i]);//其实不断在判断最优解,一层一层的
}
}
其实就是规定从m开始循环,保证了选择这个物品时,肯定不会重复使用状态。
关于完全背包:
完全背包每个物品都是无限,认死了选性价比最高的,不一定是完全填满背包的。(其实就是01背包一维数组中把j倒置)
这里的二维数组就不如一维数组了
for(int i=0;i
关于多重背包:
首先把物品拆开,把相同的num[i]件物品看成价值和重量相同的num[i]件不同的商品,那么,就转化成了一个规模稍微大一点的01背包了。
for(int i=1; i<=n; i++)//每种物品
for(int k=0; k=weight[i]; j--)//正常的01背包代码
dp[j]=max(dp[j],dp[j-weight[i]]+value[i])
以上八种案例为动态规划的经典案例,后序还会进行不定期更新!