DP常见问题及解法

1.斐波那契数列

斐波那契数列大家都很熟悉,而且知道用递归可以很容易的做出来:

n = int(input)
def fblq(n):
    if n == 0:
        return 0
    elif n == 1:
        return 1
    else:
        return fblq(n-1) + fblq(n-2)

如果用动态规划,就是把结果存到一个数组中:

n = int(input())
dp = []
for i in range(n):
    if i in [0,1]:
        dp.append(1)
    else:
        dp.append(dp[i-1] + dp[i-2])
print(dp[-1])

与之类似的还有:跳台阶问题:每次只能跳一个或者两个台阶,跳到n层台阶上有几种方法

填充长方体问题:将一个2*1的长方体填充到2*n的长方体中,有多少种方法

2.数组最大不连续递增子序列

arr = [3,1,4,1,5,9,2,6,5]的最长递增子序列长度为4。

设置一个数组dp,长度为原数组长度,数组第i个位置上的数字代表0...i上最长递增子序列,当增加一个数字时,寻找前面全部比它小的且连续性最多的个数再加1,比如数组当中的5,它比前面的数都大,但最长子序列是345:

n = int(input())
m=list(map(int,input().split()))
dp = [1]*n
for i in range(1,n):
    for j in range(i):
        if m[i] > m[j] and dp[j] + 1>dp[i]:
            dp[i] = dp[j]+1
            
print(max(dp))

当5进行比较时,虽然他比1大,但是1的dp值+1却没有4的大,因为4的dp值比前面的3大。

3.数组最大连续子序列和

如arr = {6,-1,3,-4,-6,9,2,-2,5}的最大连续子序列和为14。即为:9,2,-2,5

创建一个数组dp,长度为原数组长度,不同位置数字dp[i]代表0...i上最大连续子序列和,初始值为数组中的第一个数字。当进来一个新的数字arr[i+1]时,判断到他前面数字子序列和dp[i]+arr[i+1]跟arr[i+1]哪个大,前者大就保留前者,后者大就说明前面连续数字加起来都不如后者一个新进来的数字大,前面数字就可以舍弃,从arr[i+1]开始,每次比较完都跟max比较一下,最后的max就是最大值。

n = int(input())
m=list(map(int,input().split()))
dp = []
dp[0] = m[0]
for i in range(1,n):
    dp.append(max(dp[i-1]+m[i],m[i]))
print(max(dp))

4.数字塔从上到下所有路径中和最大的路径

数字塔是第i行有i个数字组成,从上往下每个数字只能走到他正下方数字或者正右方数字,求数字塔从上到下所有路径中和最大的路径:
 

n = int(input())
m = []
for i in range(n):
    m.append(list(map(int,input().split())))
dp = m[0]
for i in range(1,n):
    ls = m[i]
    dp1 = [0]*len(ls)
    for j in range(len(ls)):
        if j == 0:
            dp1[j] = dp[j] + ls[j]
        elif j == len(ls)-1:
            dp1[j] = ls[j] + dp[j-1]
        else:
            dp1[j] = max(dp[j-1],dp[j])+ls[j]
    dp = dp1.copy()
print(max(dp))
   


'''
3

1    5

8    4    3

2    6    7    9

6    2    3    5    1

最大路径是3-5-3-9-5,和为25。
'''     

5.两个字符串最大公共子序列

比如字符串1:BDCABA;字符串2:ABCBDAB,则这两个字符串的最长公共子序列长度为4.

详细描述点这里:点这里

n = input()
m = input()
dp = [[0]*(len(n)+1) for i in range(len(m)+1)]

for i in range(1,(len(m)+1)):
    for j in range(1,(len(n)+1)):
        if m[i-1] == n[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[-1][-1])

这里注意一下dp二维数组的创建:

dp = [[0]*(len(n)+1)]*(len(m)+1)
#该方法不行,它是相当于复制了[0]*(len(n)+1),当一行改变,全部改变
dp = [[0]*(len(n)+1) for i in range(len(m)+1)]
#该方法可行

6.背包问题

该问题也是创建一个二维dp数组,横坐标为背包可容量,纵坐标为各个物品的编号,dp[i,j]表示当前所拥有的总价值,当第i个物品可以放下时,是不是(i-1,j-wi)时的价值加上该物品的价值,与(i-1,j)的价值相比对,选择大的。如果放不下的话就取(i-1,j)。

n = int(input())#物品数量
wt = int(input())#背包总量
w = list(map(int,input().split()))#各个物品的重量
p = list(map(int,input().split()))#各个物品的价值

dp = [[0]*(wt+1) for i in range(n+1)]

for i in range(1,n+1):
    for j in range(1,wt+1):
        if j >= w[i-1]:
            dp[i][j] = max(dp[i-1][j],dp[i-1][j-w[i-1]] + p[i-1])
#这里不能用dp[i][j-w[i-1]],因为很有可能造成一件物品计算两次甚至多次
        else:
            dp[i][j] = dp[i-1][j]
    print(dp[i])
print(max(dp))

'''
6
10
2 2 3 1 5 2
2 3 1 5 4 3

[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
[0, 0, 2, 2, 2, 2, 2, 2, 2, 2, 2]
[0, 0, 3, 3, 5, 5, 5, 5, 5, 5, 5]
[0, 0, 3, 3, 5, 5, 5, 6, 6, 6, 6]
[0, 5, 5, 8, 8, 10, 10, 10, 11, 11, 11]
[0, 5, 5, 8, 8, 10, 10, 10, 12, 12, 14]
[0, 5, 5, 8, 8, 11, 11, 13, 13, 13, 15]
[0, 5, 5, 8, 8, 11, 11, 13, 13, 13, 15]
'''

可以将其优化成一个滚动数组,只创建一个一维数组,长度为从1到W,初始值都是0,能装得下i时,dp[j] = Math.max(dp[j], dp[j-w[i]]+p[i]);装不下时,dp[j] = dp[j]:

n = int(input())#物品数量
wt = int(input())#背包总量
w = list(map(int,input().split()))#各个物品的重量
p = list(map(int,input().split()))#各个物品的价值

dp = [0]*(wt+1)
for i in range(n):
    print(dp)
    for j in range(wt,0,-1):
        if j >= w[i]:
            dp[j] = max(dp[j],dp[j-w[i]]+p[i])
        else:
            dp[j] = dp[j]
'''
这里需要注意的是第二层循环一定要重后往前,不然从前往后会受前面值得影响
'''

7.找零钱问题:有几种方法

具体思路同背包问题,这里只分析一下动态转化方程,能用这种零钱,分为用了这种零钱的方法跟没用到这种零钱的方法,dp[i][j] = dp[i-1][j] + dp[i][j-num[i]](这里运算法则的原因写出来就能知道了);如果不能用这种零钱,即所组成的面额小于当前零钱,直接等于不用这种零钱的数值,dp[i][j] = dp[i-1][j]。

这里要特别注意的是:1、开始填写二维数组边界值时,第一行是填写只用第一种面额零钱组成相应数额的方法,要注意是总数额除以第一种面额取余为0才能组成,即如果第一种面额为2,不能组成3,5的数额等;2、填写二维数组第一列时,代表到用到面额为i时,剩余数额为0,即只用i就可以组成相应数额,这也是一种方法,比如2,5组成的面额,当target为5时,2是不能构成的,利用上面的算法,恰好能把第一列的1给用上,凑成了一种方法。


target = int(input())#需要换的钱
n = list(map(int,input().split()))#拥有面额
dp = [[0]*(target+1) for i in range(len(n))]
for i in range(target+1):
    if i%n[0] == 0:
        dp[0][i] = 1
for i in range(len(n)):
    dp[i][0] = 1
for i in range(1,len(n)):
    for j in range(1,target+1):
        if j >= n[i]:
            dp[i][j] = dp[i][j-n[i]] + dp[i-1][j]
        else:
            dp[i][j] = dp[i-1][j]

同背包问题一样,可以用一维数组优化:

target = int(input())#需要换的钱
n = list(map(int,input().split()))#拥有面额
dp = [0]*(target+1)
for i in range(target+1):
    if i%n[0] == 0:
        dp[i] = 1
dp[0] = 1
for i in range(1,len(n)):
    for j in range(1,target+1):
        if j >= n[i]:
            dp[j] = dp[j-n[i]]+dp[j]

8.找零钱问题:所用面额数量最少

跟上面思路相同,代码不同点:1、填写边界值时,第一行仍是看取余是不是为0,如果为0,填的是除以它的商,即用了几张。2、填写边界值第一列时,  第一列代表用了这一面额的纸币且剩下的数额为0,代表值用着一种纸币就可以构成这种数额,用的张数应该填到(i,j)处,所以第一列都是0;3、写状态转化方程时,要注意判断,如果数额小于面额,直接等于上一层值,dp[i][j]=dp[i-1][j];  如果数额等于面额,直接等于1;如果数额大于面额,先判断当用掉一张面额时,使用当前面额的剩余数额处是否有值,和不使用当前面额的剩余数额处是否有值,即当dp[i-1][j]!=0&&dp[i][j-num[i]]!=0即这两处都有值,就看那一个更小,如果不都有,仅仅选择一个有值的就好了.

target = int(input())#需要换的钱
n = list(map(int,input().split()))#拥有面额
dp = [[0]*(target+1) for i in range(len(n))]
for i in range(1,target+1):
    if i % n[0] == 0:
        dp[0][i] = int(i/n[0])
for i in range(len(n)):
    dp[i][0] = 0
for i in range(1,len(n)):
    for j in range(1,target+1):
        if j < n[i]:
            dp[i][j] = dp[i-1][j]
        elif j == n[i]:
            dp[i][j] = 1
        else:
            if dp[i-1][j] != 0 and dp[i][j-n[i]] != 0:
                dp[i][j] = min(dp[i-1][j],dp[i][j-n[i]]+1)
            else:
                if dp[i-1][j] == 0:
                    dp[i][j] = dp[i][j-n[i]]+1
                else:
                    dp[i][j] = dp[i-1][j]

滚动数组如下:

target = int(input())#需要换的钱
n = list(map(int,input().split()))#拥有面额
dp = [0]*(target+1)
for i in range(1,target+1):
    if i % n[0] == 0:
        dp[i] = int(i/n[0])
for i in range(1,len(n)):
    for j in range(1,target+1):
        if j < n[i]:
            dp[j] = dp[j]
        elif j == n[i]:
            dp[j] = 1
        else:
            if dp[j] != 0 and dp[j-n[i]] != 0:
                dp[j] = min(dp[j],dp[j-n[i]]+1)
            else:
                if dp[j] == 0:
                    dp[j] = dp[j-n[i]]+1
                else:
                    dp[j] = dp[j]


 

  • 4
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值