迅雷2018筆試——尋找組合
題目:
給定整數n,取若干個1到n的整數可求和等於整數m,程式設計求出所有組合的個數。 比如當n=6,m=8時,有四種組合:[2,6], [3,5], [1,2,5], [1,3,4]。限定n和m小於120
一開始沒有什麼解題思路,考慮在迴圈內一遍一遍算,滿足條件了Count++,實際上發現這個過程太複雜,並且要考慮重複的問題。後來發現了一個演算法叫 01揹包問題 ,實際上就是解決這個題目的思想所在。
所以下面先講這個問題:
0-1 揹包問題:給定 n 種物品和一個容量為 C 的揹包,物品 i 的重量是 Wi,其價值為 Vi 。
問:應該如何選擇裝入揹包的物品,使得裝入揹包中的物品的總價值最大?
我們可以先把這n種物品放到一個數組中用 i 代替:{ 0 , 1 , 2 , 3 , ....., i }
同樣把揹包容量放在一個數組中用 j 代替:{ 0 , 1 , 2 , 3 , ....., j }
設 m[i][j] 為當有 i 種物品,揹包容量為 j 時,揹包中物品的最大總價值。
當考慮物品 i 是否應該放入揹包容量為 j 的時候,我們可以很輕易地得出兩種情況:
(1) j < Wi 的情況,即揹包容量小於物品重量的情況。在這種情況下,很明顯不滿足條件,因此不能拿該物品。
那麼此時,揹包中物品的總價值就是:m[ i ][ j ] = m[ i-1 ][ j ] (即總價值和 i -1 種物品時的情況相同)
(2) j >= Wi 的情況,這時揹包容量可以放下第 i 件物品,我們就要考慮拿這件物品是否能獲取更大的價值。
如果拿取,在拿取該物品的情況下,揹包剩餘容量就變為 j-Wi ,那麼在這容量下,剩餘 i-1種物品的最大價值就是 m[ i-1 ][ j-Wi ] ,也是相當於為第i件物品騰出了w[i]的空間。現在加上該物品,那就加上它的價值Vi。
得出 m[ i ][ j ]=m[ i-1 ][ j-w[ i ] ] + Vi
如果不拿,m[ i ][ j ] = m[ i-1 ][ j ] , 同(1)
究竟是拿還是不拿,自然是比較這兩種情況那種價值最大:max { m[ i-1 ][ j ] , m[ i-1 ][ j-Wi ] + Vi) }
這裡,我們就推出了該演算法的核心方程,稱之為狀態轉移方程:
狀態轉移方程:m[i,j]=max { m [ i-1 , j ] , m [ i-1 , j-wi ] +vi }
可以舉個例子:
物品編號 i | 1 | 2 | 3 | 4 |
物品重量 Wi | 2 | 3 | 4 | 5 |
物品價值 Vi | 3 | 4 | 5 | 6 |
畫出下面的價值表格,可以很容易初始化(當揹包容量為 0 或者物品數量為 0 時,價值都為0):
i/j | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 |
---|---|---|---|---|---|---|---|---|---|
0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
1 | 0 | ||||||||
2 | 0 | ||||||||
3 | 0 | ||||||||
4 | 0 |
然後開始根據上面解釋過的策略開始填值:
i/j | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | ... | i |
---|---|---|---|---|---|---|---|---|---|---|---|
0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | ||
1 | 0 | 0 | 3 | 3 | 3 | 3 | 3 | 3 | 3 | ||
2 | 0 | 0 | 3 | 4 >3 = 4 | 4 | 7 | 3+4=7 > 3 | 7 | 7 | ||
3 | 0 | 0 | 3 | 4 | 5 | 7 | 5+2=8 > 7 | 5+4=9>7 | 9 | ||
4 | 0 | 0 | 3 | 4 | 5 | 7 | 8 | 9 | 10 | ||
... | ...... | ||||||||||
j | max { m[ i-1 ][ j ] , m[ i-1 ][ j-Wi ] + Vi) } |
自己跟著思路填一遍,就會有所啟發。可以自己隨意舉例填表。
言歸正傳
現在回頭看迅雷的筆試題,是不是覺得很熟悉了。
同樣可以很容易地畫出表格:
下面給出我的一個程式碼,用遞迴解決:
int Practice::ComboNumber(int m, int n)
{
if(m <1|| n<1) //m或n為0,無組合
return 0;
int count = 0;
if(m < n)
{
count += ComboNumber(m-1,n); //m-1情況的組合數
count += ComboNumber(m-1,n-m); //再加上n-1,n=n-m時候組合
}
else if(m == n)
{
count+=ComboNumber(m-1,n); //m-1情況的組合數
count++; //再加上本身為一種組合
}
else if(m>n)
count+=ComboNumber(m-1,n); //跟m-1時組合數相同
return count;
}
對照著表格,你會發現規律的。