• 动态规划算法题(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:
      

      第一次选择楼层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

    #-*- 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:101010 、5、510、5、1*510、1*10,等同于10的4种情况105、55、1*51*10,所以总共5+4=9种。

      对于25(在最大面值能使用20的情况下):首先最大面值为10的情况下,有12种情况;在最大面值为20的情况下,也就是20+5:20、520、1*5,不考虑20,等同于5的两种情况51*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]

     

  • 相关阅读:
    【Loadrunner】使用LR录制HTTPS协议的三种方法
    【Loadrunner】使用LoadRunner上传及下载文件
    【Loadrunner】平台1.9环境APP成功录制并调试成功后的脚本备份
    JavaScript命令模式
    JavaScript享元模式
    JavaScript适配器模式
    安装MySQL
    idea创建web项目,springboot项目,maven项目
    spring自定义注解
    服务器访问数据库表mysql
  • 原文地址:https://www.cnblogs.com/ybf-yyj/p/8880011.html
Copyright © 2020-2023  润新知