算法编程题总结——动态规划

1. 动态规划理论

1.1 动态规划的思想:

首先,动态规划最重要的是掌握他的思想,动态规划的核心思想是把原问题分解成子问题进行求解,也就是分治的思想。
那么什么问题适合用动态规划呢?我们通过一个现实中的例子,来理解这个问题。

大家可能在公司里面都有一定的组织架构,可能有高级经理、经理、总监、组长,然后才是小开发,今天我们通
过这个例子,来讲讲什么问题适合使用动态规划。又到了一年一度的考核季,公司要挑选出三个最优秀的员工。
一般高级经理会跟手下的经理说,你去把你们那边最优秀的3个人报给我,经理又跟总监说你把你们那边最优秀
的人报给我,经理又跟组长说,你把你们组最优秀的三个人报给我,这个其实就动态规划的思想!

1.2 基本性质

1.最优化原理(最优子结构性质):
一个最优化策略具有这样的性质,不论过去状态和决策如何,对前面的决策所形成的状态而言,余下的诸决策必须构成最优策略。简而言之,一个最优化策略的子策略总是最优的。一个问题满足最优化原理又称其具有最优子结构性质。

2.无后效性:
将各阶段按照一定的次序排列好之后,对于某个给定的阶段状态,它以前各阶段的状态无法直接影响它未来的决策,而只能通过当前的这个状态。换句话说,每个状态都是过去历史的一个完整总结。这就是无后向性,又称为无后效性。

3.子问题的重叠性:
动态规划将原来具有指数级时间复杂度的搜索算法改进成了具有多项式时间复杂度的算法。其中的关键在于解决冗余,这是动态规划算法的根本目的。在用递归算法自顶向下解问题时,每次产生的子问题并不总是新问题,有些子问题被反复计算多次。动态规划算法正是利用了这种子问题的重叠性质,对每一个子问题只解一次,而后将其解保存在一个表格中,在以后尽可能多地利用这些子问题的解。动态规划实质上是一种以空间换时间的技术,它在实现的过程中,不得不存储产生过程中的各种状态,所以它的空间复杂度要大于其它的算法。

1.3 求解步骤

求解方法:阶段 + 状态变量 + 状态转移方程 + 边界条件
1、按照问题的时间或空间特征,把问题分为若干个阶段。在划分阶段时,注意划分后的阶段一定要是有序的或者是可排序的,否则问题就无法求解。

例如上面的例子,我们可以认为每个组下面、每个部门、每个中心下面最优秀的3个人,都是全公司最优秀的3个人的子问题

2、确定状态和状态变量:将问题发展到各个阶段时所处于的各种客观情况用不同的状态表示出来。当然,状态的选择要满足无后效性。

上述例子,我们可以实用f[i][3]表示第i个人,他手下最优秀的3个人是谁。

3、确定决策并写出状态转移方程:因为决策和状态转移有着天然的联系,状态转移就是根据上一阶段的状态和决策来导出本阶段的状态。所以如果确定了决策,状态转移方程也就可写出。

上述例子,每个人大Leader下面最优秀的人等于他下面的小Leader中最优秀的人中最优秀的几个。

4、寻找边界条件:给出的状态转移方程是一个递推式,需要一个递推的终止条件或边界条件。确定初始状态是什么?最小的子问题?最终状态又是什么。

例如上述问题,最小的子问题就是每个小组长下面最优秀的人,最终状态是整个企业,初始状态为每个领导下面都没有最优名单,但是小组长下面拥有每个人的评分。

1.4 实现方式

动态规划的实现分为两种,有递归和迭代。
递归一般是自顶向下,依赖于子问题优化函数的结果,只有子问题完全求出,也就是子问题的递归返回结果,原问题才能求解。
迭代法,就是巧妙的安排求解顺序,从最小的子问题开始,自下而上求解。每次求新的问题时,子问题的解已经计算出来了。

如何理解:自底向上和自顶向下?

故事1:某日小明上数学课,他的老师给了很多个不同的直角三角板让小明用尺子去量三角板的三个边,并将长度记录下来。两个小时过去,小明完成任务,把数据拿给老师。老师给他说,还有一个任务就是观察三条边之间的数量关系。又是两个小时,聪明的小明连蹦带跳走进了办公室,说:“老师,我找到了,三条边之中有两条,它们的平方和约等于另外一条的平方。”老师拍拍小明的头,“你今天学会了一个定理,勾股定理。它就是说直角三角形有两边平方和等于第三边的平方和”。

故事2:某日老师告诉小明“今天要教你一个定理,勾股定理。”小明说,“什么是勾股定理呢?”“勾股定理是说,直角三角形中有两条边的平方和等于第三边的平方。”然后老师给了一大堆直角三角板给小明,让他去验证。两个小时后,小明告诉老师定理是正确的。

故事1是从一个用例去推导然后建模的方式是自底向上。
故事2是先给你一个抽象定理让你去验证,应用的方式是自顶向上。
两种分析方法的根本区别是:
自底向上的分析,是从具体到抽象;自顶向下的分析,是从抽象到具体。

2. 典型编程题目

2.1 硬币问题

本部分内容:参考链接
题目 1:
假设有 1 元, 3 元, 5 元的硬币若干(无限) , 现在需要凑出 11 元,问如何组合才能使硬币的数量最少?
换一种表达方式:给定总金额为A的一张纸币,现要兑换成面额分别为a1,a2,…,an的硬币,且希望所得到的硬币个数最少。
跟完全背包题目7的区别:
本题可看成是数量若干的物品,怎么样才能恰好装满书包,使得价值最大?且体积等于价值
这样又类似于装箱问题题目6

拓展1:
假设书包容量是11,物品1,物品2,物品3的体积为1,3,5,价值也分别是1,3,5,怎么样使得书包装满且价值最大?
与本题的程序是一样的

思想:
假设一个函数 dp(i) 表示需要凑出 i 的总价值需要的最少硬币数量,那么我们不难想到:
当 i = 0 时, dp(0) = 0
当 i = 1 时, dp(1) = 1
当 i = 2 时, dp(2) = 2
当 i = 3 时, dp(3) = 1
依此类推……
可以看出,除了第 1 步,其他往后的结果都是建立在它之前得到的某一步的最优解上,加上 1 个硬币得到,因此可以得出:d(i) = d(j) + 1, j < i。通俗地讲,如果我们需要凑出 i 元,就在凑出 j 的结果上再加上某一个硬币就行了。加哪个硬币呢?
假设最后加上的是 1 元硬币,那 dp(i) = dp(j) + 1 = dp(i - 1) + 1。
假设最后加上的是 3 元硬币,那 dp(i) = dp(j) + 1 = dp(i - 3) + 1。
假设最后加上的是 5 元硬币,那 dp(i) = dp(j) + 1 = dp(i - 5) + 1。
因此,分别计算出 dp(i - 1) + 1,dp(i - 3) + 1,dp(i - 5) + 1 的值,取其中的最小值,即为最优解 d(i),状态转移方程:
dp[i] = min{dp[i - coins[j]] + 1}, 其中 i >= coins[j], 0 <= j < coins.length
备注:path[i] 的逻辑还不是很懂?

# 动态规划思想  dp方程式如下
# dp[0] = 0
# dp[i] = min{dp[i - coins[j]] + 1}, 且 其中 i >= coins[j], 0 <= j < coins.length
# 回溯法,输出可找的硬币方案
# path[i] 表示经过本次兑换后所剩下的面值,即 i - path[i] 可得到本次兑换的硬币值。
def changeCoins(coins, n):
    if n < 0: return None
    dp, path = [0] * (n + 1), [0] * (n + 1)  # 初始化
    for i in range(1, n + 1):
        minNum = i  # 初始化当前硬币最优值
        for c in coins:  # 扫描一遍硬币列表,选择一个最优值
            """
            if i >= c:
                minNum = min(minNum,dp[i - c] + 1)
            """
            if i >= c and minNum > dp[i - c] + 1:
                minNum, path[i] = dp[i - c] + 1, i - c
        dp[i] = minNum  # 更新当前硬币最优值
 
    print('最少硬币数:', dp[-1])
    print('可找的硬币', end=': ')
    while path[n] != 0:
        print(n - path[n], end=' ')
        n = path[n]
    print(n, end=' ')
if __name__ == '__main__':
    coins, n = [1, 3, 5], 11 # 输入可换的硬币种类,总金额n
    changeCoins(coins, n)

在这里插入图片描述
题目 2:
有数量不限的硬币, 币值为25分、 10分、 5分和1分, 请编写代码计算n分有几种表示法。
跟题目1的区别:
换成n元有多少组合,题目1是数量最小的组合,所以题目1是题目2解中的一个特例。

拓展2:
本题也可看成是数量若干的物品,有多少种装法恰好装满书包?

思想:
当只有1分的硬币时, n从1到n分别有多少种表示方法;
当有1分和5分的硬币时, n从1到n分别有多少种表示方法;
依次类推, 直到我们将1分、5分、 10分和25分的硬币全部使用完, 思想类似于0-1背包问题。
用数组coins[i] = {1,5,10,25}表示各种币值, 假设ways[i][j]代表能用前i种硬币来表示j分的方法数目。此时可以得到一张二维表ways[i][j],其中横坐标表示前i种表示币值, j表示硬币的总值。
当增加一种新的硬币币值时, 有两种情况:
1、若不加入此种币值: ways[i][j]=ways[i-1][j];
2、若加入此种币值: 加入该枚硬币之前的方法数为ways[i][j-coins[i]],那么加入该枚硬币之后构成 j 分的方法数也为ways[i][j-coins[i]] (总分加了coin[i],即在原来的方法中每个组合依次+coin[i],故组合数没变)。
因此当增加一种新的币值时, j 分的表示方法数为ways[i][j]=ways[i-1][j](j分需要前i-1币的表示方法)+ways[i][j-coins[i]](j-coins[i]分需要前i币的表示方法)。

硬币\分数 0 1 2 3 4 5
coin1 1 1 1 1 1 1
coin2 1 1 2 2 3 3
coin3 1 1 2 3 4 5

例如有3种币值1,2,3,表示5分有几种?
先画出3X6的矩阵,初始条件假设第一行第一列均为1。
转移方程红色标注的,当总分为1时,需要1个coin1,即ways[0][1]=1,coin2和coin3不能用,所以不加入(1种情况),故ways[1][1]=1,ways[2][1]=1。当总分为2时,ways[0][2]=1,考虑加入coin2,则ways[1][2]=ways[0][2](代表总分2选币值1有1种)+ways[1][2-2](代表总分0都选币值2有1种)=1+1=2。

def changeCoins2(coins, n):
    len1 = len(coins)
    if len1 == 0 and n < 0:
        return None
    ways = [[0] * (n+1) for row in range(len1)]
    for i in range(len1):
        ways[i][0] = 1  # 第1行初始化为1
    for j in range(1, n+1):
        ways[0][j] = 1  # 第1列初始化为1
    for i in range(1, len1):
        for j in range(1, n+1):
            if j>=coins[i]:
                ways[i][j] = ways[i - 1][j] + ways[i][j - coins[i]]
            else:
                ways[i][j] = ways[i - 1][j]
    print('\n假设有数量不限的硬币, 币值为{0}, 则{1}分共有{2}种表示法'.format(coins, n, ways[len1 - 1][n]))
    #对应上面表格的矩阵
    print(ways)
if __name__ == '__main__':
    coins, n = [1, 2,3], 5  # 输入可换的硬币种类,总金额n
    changeCoins2(coins, n)

在这里插入图片描述

2.2 爬楼梯问题

题目 3:
一只青蛙一次可以跳上1级台阶,也可以跳上2级。求该青蛙跳上一个n级的台阶总共有多少种跳法(先后次序不同算不同的结果)
分析和实现(递归+迭代)请见:第8题

再深度分析:
依次递推,得到递推公式为:f(n) = f(n - 1) + f(n - 2) , n>=3。
因此,这个题的本质就是斐波那契数列!但又不完全是,我们知道,这个数列可以用递归函数来表示,也可以用迭代来进行计算,前者属于自顶向下的模式(简洁明了),后者属于自底向上的模式(简单高效),面试过程中建议两者皆会!实际工程中常用迭代的方法!

题目 4:
一只青蛙一次可以跳上1级台阶,也可以跳上2级,也可以跳上3级。求该青蛙跳上一个n级的台阶总共有多少种跳法(先后次序不同算不同的结果)
若该题目中的条件换成先后次序可以看作是相同的结果,则可以转化为硬币问题题目2。
拓展3:
有数量不限的硬币, 币值为1分、 2分、 3分, 一直到n分, 请编写代码计算n分有几种表示法。
分析和实现 请见:第9题

2.3 装箱问题

本部分内容:参考链接
题目 5:
有一个箱子容量为V(正整数, 0<=V<=20000), 同时有n个物品(0<n<=30), 每个物品有一个体积(正整数), 从n个物品中,任取若干个装入箱内,使箱子的剩余空间为最小。

思想:
属于背包型动态规划,相当于背包容量和背包中物品价值二者相等的一般背包问题(貌似也称为伪背包问题)。通过转化思想即求:在总体积为V的情况下,可以得到的最大价值,最后再用总体积减去最大价值时所占空间就是剩下的最少空间。
假设每个物品i的体积为Vi,i=1,2,…,n,dp[ i ][ j ]表示前 i 件物品装入体积为 j 的箱子,数值为体积j。一共n件物品,那么dp[ n ][ V ]就是前 n 件物品选择部分装入体积为V的箱子后,数值为体积V。
1.当前输入的物品体积大于箱子容量剩余空间 j :则不装箱,dp[ i ][ j ] = dp[i - 1][ j ];
2.当前输入的物品体积小于等于箱子容量剩余空间 j ,则需要考虑装与不装两种状态,取体积最大的那一个:dp[ i ][ j ] = max( dp[i - 1][ j ](没装箱),dp[ i - 1 ][ j - Vi ] + Vi (装箱) )。
装箱的理解:dp[ i - 1 ][ j - Vi ] 表示装了j-Vi体积的物品是前i-1种物品
此处与硬币问题不同ways[i][j-coins[i]],没有i-1的原因是可以允许相同面值的硬币,装箱问题是n个不同的物品,体积也不一定相同。

物品\所占体积 0 1 2 3 4 5 6
物品0 0 0 0 0 0 0 0
物品1 0 0 0 3 3 3 3
物品2 0 0 0 3 4 4 4
def solveBinPacking(V, arr):
    len1 = len(arr)
    if V<=0 and len1 == 0:
        return None
    dp = [[0]*(V+1) for row in range(len1+1)] # 初始化
    for i in range(1, len1 + 1):
        t = arr[i-1]#第i个物品的体积
        for j in range(1, V+1):
            if j>= t:
                #比较装与不装的大小
                dp[i][j] = max(dp[i-1][j], dp[i-1][j-t] + t)
            else:
                #不装箱
                dp[i][j] = dp[i-1][j]
    return V-dp[len1][V]
 
if __name__ == '__main__':
    V = int(input()) # 最大体积
    n = int(input()) # 物品数量
    arr=list(map(int,input().strip().split()))
    print(solveBinPacking(V, arr))

在这里插入图片描述

2.4背包问题

本部分内容:参考链接

2.41 0-1背包问题

题目 6:
现有n件物品和一个容量为c的背包。第i件物品的重量是重量为w[i],价值是v[i]。已知对于一件物品必须选择取(用1表示)或者不取(用0表示),且 每件物品只能被取一次(这就是“0-1”的含义)。求放置哪些物品进背包,可使这些物品的重量总和不超过背包容量,且价值总和最大。

思想:与上一个装箱问题类似,只是体积和价值不一定相等,dp[i][j] 表示只看前i个物品,总体积是j的情况下,总价值最大是多少。

输入格式:
第一行两个整数,N,V,用空格隔开,分别表示物品数量和背包容积。
接下来有 N 行,每行两个整数 vi,wi,用空格隔开,分别表示第 i 件物品的体积和价值。
输出格式:
输出一个整数,表示最大价值。
N, V = map(int, input('输入物品的数量和背包的容量').split())
print('二维矩阵')
goods = []
# 是一个二维列表,第一列为每个物品的体积,第二列为每个物品的价值
for i in range(N):
    goods.append([int(i) for i in input().split()])
# 初始化,先全部赋值为0,这样至少体积为0或者不选任何物品的时候是满足要求  
dp = [[0 for i in range(V+1)] for j in range(N+1)]
for i in range(1, N+1):
    for j in range(1,V+1):
        t=goods[i-1][0]  # 第i个物品体积
        tv=goods[i-1][1] # 第i个物品价值
        if j>=t:# 判断背包容量是不是大于第i件物品的体积
            # 在选和不选的情况中选出最大值
            dp[i][j] = max(dp[i-1][j], dp[i-1][j-t]+tv)
        else:
            dp[i][j] = dp[i-1][j]    
print(dp[-1][-1])

算法编程题总结——动态规划_第1张图片
改进:若题目的问题改成可使这些物品的重量等于背包容量,且价值总尽可能最大,该怎么做呢?
体积等于价值,是题目1的扩展,体积!=价值,待解决,希望有想法的可以互相交流~

2.42 完全背包问题

题目 7:
有N件物品和一个容量为V的背包, 每件物品都有无限个。
第i件物品的体积是vi,价值是wi。
求解将哪些物品装入背包,可使这些物品的总体积不超过背包流量,且总价值最大。

思想:
完全背包问题跟0-1背包问题最大的区别就是每一个物品可以选无数次,因此当我们考虑到第i个物品时,我们应该考虑的情况是:不选这个物品、选一次这个物品、选两次这个物品…,直到不能再选(选的次数k,k*v[i] > j,j为当前背包容量),然后再从这些情况中选价值最大的。

输入格式:
第一行两个整数,N,V,用空格隔开,分别表示物品种类和背包容积。
接下来有 N 行,每行两个整数 vi,wi,用空格隔开,分别表示第 i 种物品的体积和价  值。
输出格式:
输出一个整数,表示最大价值。
n, v = map(int, input('输入物品的种类和背包的容量').split())
print('二维矩阵')
goods = []
for i in range(n):
    goods.append([int(i) for i in input().split()])
dp = [[0 for i in range(v+1)] for j in range(n+1)]

for i in range(1, n+1):
    for j in range(1,v+1):
        t=goods[i-1][0]  # 第i个物品体积
        tv=goods[i-1][1]
        # 当k=0,我们不放这个物品,当k=1,2,3表示我们放k个此物品       
        x=j//t
        dp[i][j] = max([dp[i-1][j-t*k]+k*tv for k in range(x+1)])
print(dp[-1][-1])

在这里插入图片描述

2.43 多重背包问题

题目 8:
有N件物品和一个容量为V的背包。
第i件物品的体积是vi,价值是wi, 数量是si。
求解将哪些物品装入背包,可使这些物品的总体积不超过背包流量,且总价值最大。

思想:
多重背包问题跟完全背包问题最大的区别就是 选的次数k= min(s[i], j//v[i]), j为当前的背包容量j为当前背包容量,然后再从这些情况中选价值最大的。

输入格式:
第一行两个整数,N,V,用空格隔开,分别表示物品种类和背包容积。
接下来有 N 行,每行两个整数 vi,wi,用空格隔开,分别表示第 i 种物品的体积和价  值。
输出格式:
输出一个整数,表示最大价值。
n, v = map(int, input('输入物品的种类和背包的容量').split())
print('二维矩阵')
goods = []
for i in range(n):
    goods.append([int(i) for i in input().split()])
dp = [[0 for i in range(v+1)] for j in range(n+1)]

for i in range(1, n+1):
    for j in range(1,v+1):
        t=goods[i-1][0]  # 第i个物品体积
        tv=goods[i-1][1]  
        s=goods[i-1][2]  
        x = min(s, j//t)#当前j能放多少物品i和能放多少选最小的
        dp[i][j] = max([dp[i-1][j-t*k]+k*tv  for k in range(x+1)])
print(dp[-1][-1])

算法编程题总结——动态规划_第2张图片

2.5 最大递增(长上升)子序列问题(LIS)

题目9:
最长上升子序列问题(LIS),给定n个整数A1,A2,…,An,A1,按从左到右的顺序选出尽量多的整数,组成一个上升子序列。 例如序列1, 6, 2, 3, 7, 5,可以选出上升子序列1, 2, 3, 5,也可以选出1, 6, 7,但前者更长。选出的上升子序列中相邻元素不能相等。

思想:
子序列可以理解为:删除0个或多个数,其他数的顺序不变,
假设dp[ i ]表示以标识为 i 的元素为递增序列结尾元素的最长递增子序列的长度,由于这里的递增序列不要求严格相邻,因 此 arr[ i ]需要和每一个arr[ j ] ( i > j ) 比较,该方法的算法复杂度为O(N^2):
若存在 arr[ i ] > arr[ j ],说明第 i 个元素可以接在第 j 个元素后面作为新的递增序列的结尾,即dp[ i ] = max(dp[ j ])+1 = max(dp[ j ] + 1);
若存在 arr[ i ] <= arr[ j ],说明第 i 个元素比前面的数都小,此时以 i 元素作为结尾的递增序列长度为1,即dp[ i ] = 1;

最后,取出dp中最大的值就是最长的递增子序列的长度。
因此,状态转移方程为: 当 arr[ i ] >= arr[ j ] 且 j < i时,dp[ i ] = max{1, dp[ j ] + 1}

 以一个例子为例:2 3 1 5
 1.对于2,最长递增子序列为1
 2.对于3,最长递增子序列为2
 3.对于1,最长递增子序列为2,3,但该处因为相当于和前面的断开了,所以应该  定义此处的最长递增子序列为1
 4.对于5,如果和前面的1连接,最长递增子序列为1,5,长度为2;如果和前面的3连接,最长递增子序列为2,3,5,长度为3
 综上所述,最长递增子序列为2,3,5,长度为3
def lengthOfLIS(nums):
    if nums == []:
        return 0
    N = len(nums)
    Dp = [1] * N
    for i in range(1,N ):
        for j in range(0, i ):
            if nums[i] > nums[j]:
                Dp[i] = max(Dp[i], Dp[j] + 1)
    print(max(Dp))
if __name__ == '__main__':
    arr = [int(i) for i in input('输入序列:').split()]
    lengthOfLIS(arr)

在这里插入图片描述

2.6 最长公共子序列问题(LCS)

字符序列的子序列是指从给定字符序列中随意地(不一定连续)去掉若干个字符(可能一个也不去掉)后所形成的字符序列。令给定的字符序列 X = x 0 , x 1 , … , x m − 1 X = x_0,x_1,…,x_{m-1} X=x0,x1,,xm1,序列 Y = y 0 , y 1 , … , y k − 1 Y = y_0,y_1,…,y_{k-1} Y=y0,y1,,yk1 X X X 的子序列,存在 X X X 的一个严格递增下标序列 i 0 , i 1 , … , i k − 1 i_0,i_1,…,i_{k-1} i0,i1,,ik1,使得对所有的 j = 0 , 1 , … , k − 1 j=0,1,…,k-1 j=0,1,,k1,有 x i j = y j x_{ij} = y_j xij=yj。例如,X=“ABCBDAB”,Y=“BCDB”是X的一个子序列。

题目10:
假设序列A = [B, D, C, A, B, A],序列B = [A, B, C, B, D, A, B]。M与N分别表示序列A与B的长度。我们来看看怎么得到最长公共子序列LCS = [B, C, B, A]。这里需要说明的是最长公共子序列的答案并不唯一,但是最长公共子序列的长度唯一,因此一般求得都是长度!!!

思想:
假设dp[ i ][ j ]表示A序列中前 i 个字符与B序列中前 j 个字符的最大公共子序列长度,那么:
当 i = 0 或 j = 0 时,dp[ 0 ][ 0 ] = 0;
当 i > 0, j > 0 且 A[ i ] = B[ j ] 时,dp[ i ][ j ] = dp[ i - 1 ][ j - 1 ] + 1;
当 i > 0, j > 0 且 A[ i ] != B[ j ] 时,dp[ i ][ j ] = max( dp[ i - 1 ][ j ], dp[ i ][ j - 1 ]);
因此,最后的最长公共子序列长度为:dp[ M ] [ N ]。

def lengthOfLongestCommonSubsequence(arrA, arrB):
    if arrA == [] or arrA == []:
        return 0
    M, N = len(arrA), len(arrB)
    #创建2维矩阵存储公共序列长度
    dp = [[0]*(N + 1) for row in range(M + 1)]
    for i in range(1, M + 1):
        for j in range(1, N + 1):
            if arrA[i - 1] == arrB[j - 1]:
                dp[i][j] = dp[i-1][j-1] + 1
            else:
                dp[i][j] = max(dp[i-1][j], dp[i][j-1])
    print(dp[M][N])
 
if __name__ == '__main__':
    arrA = [i for i in input().split()]
    arrB = [i for i in input().split()]
    lengthOfLongestCommonSubsequence(arrA, arrB)

在这里插入图片描述

2.7 最长公共子串问题

题目11:
给定两个字符串,求出它们的最长公共子串(连续)。
与最长公共子序列唯一的区别就是这里要求连续的!

思想:
假设dp[ i ][ j ]表示A串中的前 i 个字符与 B 串中的前 j 个字符的最长公共子串的长度,那么:
当 i = 0 或 j = 0 时,dp[ 0 ][ 0 ] = 0;
当 i > 0, j > 0 且 A[ i ] = B[ j ] 时,dp[ i ][ j ] = dp[ i - 1 ][ j - 1 ] + 1;
当 i > 0, j > 0 且 A[ i ] != B[ j ] 时,dp[ i ][ j ] = 0;(连续一旦不等重新开始)

因此,最后的最长公共子串长度为:max(dp[ M ] [ N ])。因为遇到不同时就从0开始,那dp的最后的元素并不一定是最大的,即dp中长度最大的值就是最长公共子串的长度。

def lengthOfLongestCommonSubstring(arrA, arrB):
    M, N = len(arrA), len(arrB)
    if M == 0  or N == 0:
        return 0
    maxValue = 0#一直保留dp中最大的值
    dp = [[0]*(N + 1) for row in range(M + 1)]
    for i in range(1, M + 1):
        for j in range(1, N + 1):
            if arrA[i - 1] == arrB[j - 1]:
                dp[i][j] = dp[i-1][j-1] + 1
            else:
                dp[i][j] = 0
            if maxValue < dp[i][j]:
                maxValue = dp[i][j]
    print(maxValue)
 
if __name__ == '__main__':
    arrA = input()
    arrB = input()
    lengthOfLongestCommonSubstring(arrA, arrB)

在这里插入图片描述

2.8最大连续子序列(子串)求和问题

题目12:
给定K个整数的序列 N 1 , N 2 , . . . , N k {N_1,N_2,...,N_k} N1,N2,...,Nk,其中任意连续子序列可表示为 N i , N i + 1 , . . . , N j {N_i,N_{i+1},...,N_j} Ni,Ni+1...,Nj,其中 1 < = i < = j < = k 1 <= i <= j <= k 1<=i<=j<=k。最大连续子序列是所有连续子序列中元素和最大的一个,例如给定序列{-2,11,-4,13,-5,-2},其最大连续子序列为{11,-4,13},最大连续子序列和为20。

思想:
假设dp[ i ] 表示以A[ i ] 为子序列末端的最大连续和,因为dp[ i ]要求必须以A[ i ]结尾的连续序列,那么只有两种情况:
1.最大连续序列只有一个元素,即以A[i]开始,以A[ i ]结尾 ,最大和就是A[ i ]本身
2.最大和的连续序列有多个元素,即以A[ p ]开始(p小于i),以A[ i ]结尾,最大和是dp[ i - 1 ] + A[ i ] 。
因此状态转移方程为:dp[ i ] = max ( dp[ i -1] + A[ i ],A[ i ] )
最后,连续子序列的和为:maxsub[ n ] = max ( dp [ i ] ),1 <= i <= n

def maxsum(nums):
    if len(nums) == 1:# 判断序列长度,若为1,直接返回
        return nums[0]
    dp=[0 for i in range(len(nums)+1)]
    dp[0] =nums[0]
    for i in range(1,len(nums)):
    	#要么是自己1,要么是和前面的一些值2
        dp[i] = max(nums[i],dp[i-1] + nums[i])
    print(max(dp))
 
if __name__ == '__main__':
    arr = [int(i) for i in input().split()]
    maxsum(arr)

在这里插入图片描述

你可能感兴趣的:(算法)