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: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]

