动态规划算法题(5题)
1、题目描述(网易)
有 n 个学生站成一排,每个学生有一个能力值,牛牛想从这 n 个学生中按照顺序选取 k 名学生,要求相邻两个学生的位置编号的差不超过 d,使得这 k 个学生的能力值的乘积最大,你能返回最大的乘积吗?
输入描述:
每个输入包含 1 个测试用例。每个测试数据的第一行包含一个整数 n (1 <= n <= 50),表示学生的个数,接下来的一行,包含 n 个整数,按顺序表示每个学生的能力值 ai(-50 <= ai <= 50)。接下来的一行包含两个整数,k 和 d (1 <= k <= 10, 1 <= d <= 50)。输出描述:
输出一行表示最大的乘积。
试题分析:
本题要使用动态规划来解,动态规划的特点:1.求解的是最优化问题;2.可以分解为最优子结构
本题可以先求解在第i个学生的位置下,j(j<K)个学生的能力值的最大值,得到所有学生位置下j个学生的能力值的最大值;在j个学生的情况下,得到j+1个学生的最大值,最后得到k个学生的最大值,下面以一个例子来解释(注意因为有负数,所以要计算最小值,同时保存):
样例输出:
10
8 7 2 -7 9 5 4 10 -7 1
3 3
输出:
630
如上,第一步先计算k=2的情况:
7:在d=3的情况下,最大最小值都为56
2:在d=3的情况下,最大值为16,最小值为14
-7:在d=3的情况下,最大值为-14,最小值为-56
......
得到第一趟的结果
k=3的情况下(这里以第一趟的结果为基础,只有这样就不需要考虑第一趟中d=3的限制):
2:在d=3的情况下,最大最小值都为112(56*2)
-7:在d=3的情况下,最大值为-98(14*-7)最小值为-392(56*-7)
9:在d=3的情况下,最大值为504(56*9)最小值为-504(-56*9)
......
得到第二趟的结果
返回最大值就是最后的结果
#-*- coding:utf-8 -*-
n=input()
array=[int(i) for i in raw_input().split()]
k,d=[int(i) for i in raw_input().split()]
# n=36
array_max=array_min=array
#轮询k-1趟即可
for i in range(0,k-1):
_max=[-float('inf')]*n#将最大值的数组赋值无穷小
_min=[float('inf')]*n#将最小值的数组赋值无穷大
for j in range(i+1,n):
if j<=d+i:#下面对应的min、max都是考虑到array[j]为负值的情况下
temp_max = max(max(ii*array[j] for ii in array_max[i:j]),max(ii*array[j] for ii in array_min[i:j]))
temp_min = min(min(ii*array[j] for ii in array_max[i:j]),min(ii*array[j] for ii in array_min[i:j]))
else:
temp_max = max(max(ii*array[j] for ii in array_max[j-d:j]),max(ii*array[j] for ii in array_min[j-d:j]))
temp_min = min(min(ii*array[j] for ii in array_max[j-d:j]),min(ii*array[j] for ii in array_min[j-d:j]))
_max[j]=temp_max
_min[j]=temp_min
array_max=_max
array_min=_min
print array_max
print array_min
print max(array_max)
2、题目描述(腾讯):
腾讯大厦有39层,你手里有两颗一抹一眼的玻璃珠。当你拿着玻璃珠在某一层往下扔的时候,一定会有两个结果,玻璃珠碎了或者没碎。大厦有个临界楼层。低于它的楼层,往下扔玻璃珠,玻璃珠不会碎,等于或高于它的楼层,扔下玻璃珠,玻璃珠一定会碎。玻璃珠碎了就不能再扔。现在让你设计一种方式,使得在该方式下,最坏的情况扔的次数比其他任何方式最坏的次数都少。也就是设计一种最有效的方式。
试题分析:
本题要考虑最坏情况,同时也要保证成功,就像如果一开始选择20层,结果只有两个,碎和没碎两个结果,如果碎了,那么就必须从1楼开始,这样才能成功,最坏也就是20次(正好也就是20层),也就是考虑最坏情况下。
对于本题:
先选择一个楼层x,扔一个球,:
球碎,那么再从一楼开始扔球,直到球碎求到结果,即最坏会扔x次
球没碎,再选楼层y扔球:
球碎,那么从x+1层开始,直到球碎,即最坏会扔:1(x层那一次)+y-x+1(y层那一次)次
球没碎,再选楼层z扔球:
球碎:那么从y+1层开始,直到球碎,即最坏会扔:1(x层那一次)+1(y层那一次)+z-y+1(z层那一次)次
......
解题思路1:
![](https://images2018.cnblogs.com/blog/1206020/201804/1206020-20180420151812274-779350370.png)
第一次选择楼层x,第二次选择x+x-1层,第三次选择x+x-1+x-2......可以得到x=9
解题思路2:
因为是最坏情况,所以最好的情况为3次,最坏情况还是9次,这里从最高处开始考虑,39、39-1、39-2-1、39-3-2-1......
3、题目描述(网易)
小易来到了一条石板路前,每块石板上从1挨着编号为:1、2、3.......
这条石板路要根据特殊的规则才能前进:对于小易当前所在的编号为K的 石板,小易单次只能往前跳K的一个约数(不含1和K)步,即跳到K+X(X为K的一个非1和本身的约数)的位置。 小易当前处在编号为N的石板,他想跳到编号恰好为M的石板去,小易想知道最少需要跳跃几次可以到达。
例如:
N = 4,M = 24:
4->6->8->12->18->24
于是小易最少需要跳跃5次,就可以从4号石板跳到24号石板
试题分析:
初始化一个数组[0~m],置为-∞,先求n下能到达的位置,将对应的step[i]置为1,接着从n+1开始,若step[i+1]为-∞,跳过,否者将能到达的step[i]置为(step[n+1]+1,step[i])min,直至得到m为止,跳出循环,或者循环结束,返回-1。
输入
4 24
输出
5
![](https://images2018.cnblogs.com/blog/1206020/201805/1206020-20180508172118477-211435662.png)
#-*- coding:utf-8 -*-
import math
#求对应数的因子数
def get_list(i,m):
list = [] #能到达的石阶
num = int(math.sqrt(i)) + 1
for k in range(2, num):
if i % k == 0:
t=i/k #对应的因子
if i+t<=m:
list.append(i+t)
if i+k<=m:
list.append(i+k)
list.sort()
return list
if __name__=='__main__':
n,m=[int(i) for i in raw_input().split(' ')]
l=[float('inf') for i in range(0,m+1)]
l[n]=0
for i in range(n,m+1):
if l[i]==float('inf'):continue
list=get_list(i,m)
for j in list:
l[j]=min(l[j],l[i]+1)
print l
if l[m]!=float('inf'):
break
if l[m]==float('inf'):
print -1
else:
print l[m]
4、 题目描述(滴滴出行)
给定一个有n个正整数的数组A和一个整数sum,求选择数组A中部分数字和为sum的方案数。
当两种选取方案有一个数字的下标不一样,我们就认为是不同的组成方案。输入描述:
输入为两行:
第一行为两个正整数n(1 ≤ n ≤ 1000),sum(1 ≤ sum ≤ 1000)
第二行为n个正整数A[i](32位整数),以空格隔开。输出描述:
输出所求的方案数
试题分析:
本题利用递归复杂度太大,利用动态规划比较好,如下面的示例,sum为15的方案为{2、3、5、5}{2、3、10}{5、10}{5、10},利用动态规划求每一个数被组成的可行方案,如下图:
输入
5 15 5 5 10 2 3
输出
4
#-*- coding:utf-8 -*-
if __name__=='__main__':
n,s=[int(i) for i in raw_input().split(' ')]
#不需要添加大于S的数
l=[int(i) for i in raw_input().split(' ') if int(i)<=s]
#添加长度为s+1的数组,即0~s
result=[0 for i in range(0,s+1)]
#序号0置为1
result[0]=1
for i in range(0,len(l)):
temp=result[:]
for j in range(0,s+1):
if result[j]>0:
#防止数组越界
if j+l[i]<=s:
temp[j+l[i]]+=result[j]
result=temp[:]
print result[s]
5、题目描述(美团点评)
有一个X*Y的网格,小团要在此网格上从左上角到右下角,只能走格点且只能向右或向下走。请设计一个算法,计算小团有多少种走法。给定两个正整数int x,int y,请返回小团的走法数目。
输入描述:
输入包括一行,逗号隔开的两个正整数x和y,取值范围[1,10]。
输出描述:
输出包括一行,为走法的数目
示例:
输入
3 2
输出
10
试题分析:该题是一个很典型的动态规划问题,但第一想到的可能是用深度优先遍历去实现,也就是递归,用递归思维很简单,从起点开始从右、下分别出发,直到结束点;用动态规划的思想是,到点[x,y](这里将网格点看成二维数组)的走法是到点[x-1,y]和点[x,y+1]的和,从[x-1,y]往下走就到了[x,y],[x,y-1]往右走就到了[x,y]。递归实现代码如下:
# -*- coding:utf-8 -*- def fun(x,y): global n,m,num if x==n or y==m: num+=1 return elif x<n and y<m: fun(x+1,y) fun(x,y+1) if __name__ == '__main__': n,m=map(int,raw_input().split(' ')) num=0 fun(0,0) print num
动态规划实现代码如下:
# -*- coding:utf-8 -*- if __name__ == '__main__': x, y = map(int, raw_input().strip().split()) d = [[0 for j in range(y + 1)] for i in range(x + 1)] for j in range(y + 1): d[0][j] = 1 for i in range(x + 1): for j in range(y + 1): d[i][j] = d[i - 1][j] + d[i][j - 1] print d[x][y]
6、题目描述(美团点评)
给你六种面额1、5、10、20、50、100元的纸币,假设每种币值的数量都足够多,编写程序求组成N员(N为0-10000的非负整数)的不同组合的个数
输入描述:
输入为一个数字N,即需要拼凑的面额
输出描述:
输出也是一个数字,为组成N的组合个数。
示例:
输入
5
输出
2
试题分析:本题最简单做法就是for循环,但是太耗时,利用动态规划是最好的选择,见下图:(大佬详解本题)
这里举两个例子讲解一下基本思想:
对于20(在最大面值能使用10的情况下):首先最大面值为5的情况下,有5种情况(这个是上一步这里不考虑);在最大面值为10的情况下,也就是10+10:10、10,10 、5、5;10、5、1*5;10、1*10,等同于10的4种情况10,5、5,5、1*5,1*10,所以总共5+4=9种。
对于25(在最大面值能使用20的情况下):首先最大面值为10的情况下,有12种情况;在最大面值为20的情况下,也就是20+5:20、5,20、1*5,不考虑20,等同于5的两种情况5、1*5 所以总共12+2=14种。
代码实现如下:
# -*- coding:utf-8 -*- if __name__ == '__main__': n=input() print [i for i in range(0,n+1)] l=[1 for i in range(n+1)] for i in [5,10,20,50,100]: temp=l[:] for j in range(i,n+1): if j%5!=0: temp[j]=temp[j-1] else: temp[j]=l[j]+temp[j-i] print temp l=temp print l[n]