昨天詳解了一下揹包問題,以後有人問我若是每種元素均可以選擇任意數目那會怎麼樣?這是很常見的揹包問題的變種問題,只須要咱們在原來的算法基礎上作一點小小的改動,咱們一塊兒來看下。java
照例來看下題目定義:給定N種水果的重量跟收益,咱們須要把它們放進一個可容重量爲C的揹包裏,使得包裏的水果在總重量不超過C的同時擁有最高的收益,假設水果數量無限,一種可選多個
。c++
此次咱們還要去賣水果,在攜帶量有限的狀況下得到最大的收益。假設檔狀況是: 水果: { 蘋果, 橙子, 西瓜 }
重量: { 1, 2, 3 }
收益: { 15, 20, 50 }
可容重量: 5
。算法
咱們也一樣先來稍稍列舉下可能的狀況: 5 蘋果 (總重 5) => 75 收益
1 蘋果 + 2 橙子 (總重 5) => 55 收益
2 蘋果 + 1 西瓜 (總重 5) => 80 收益
1 橙子 + 1 西瓜 (總重5) => 70 收益
。數組
咱們能夠看到兩個蘋果跟西瓜是絕配,載重量有限的狀況下咱們得到了最大收益。關鍵是咱們得把這個過程經過代碼表達出來,咱們來分析一下,對於每種水果,咱們能夠選擇放進去而後進行下一輪選擇,或者不放進去直接進行下一輪選擇,在每次放進去一種水果A以後,咱們還要選擇要不要把A再放進去,知道超出揹包的載重量,而後在這個過程當中咱們要選出兩種選擇中帶來最大收益的那個。緩存
也照舊,咱們先用遞歸來把算法實現出來,後期再慢慢優化。上面已經描述得很清楚了,咱們能夠直接寫出來:post
private int knapsackRecursive(int[] profits, int[] weights, int capacity, int currentIndex) {
if (capacity <= 0 || profits.length == 0 || weights.length != profits.length ||
currentIndex >= profits.length)
return 0;
// 選擇了當前元素以後繼續循環處理,要注意這裏選擇結束後並無把索引+1
int profit1 = 0;
if (weights[currentIndex] <= capacity)
profit1 = profits[currentIndex]
+ knapsackRecursive(profits, weights, capacity - weights[currentIndex], currentIndex);
// 跳過當前元素而後繼續作選擇
int profit2 = knapsackRecursive(profits, weights, capacity, currentIndex + 1);
return Math.max(profit1, profit2);
}
複製代碼
想必你們都看的出來,咱們的算法跟昨天的很類似,除了一些條件的變化。要注意的是這裏的時間複雜度變成了O(2^(N+C)),N是元素元素數量,C是揹包最大載重,由於咱們能夠重複選擇某一元素。優化
如今遇到這種問題,寫出了暴力遞歸的作法,你們確定都能條件反射般地用緩存來優化算法了。這邊已經不須要我賣關子了,我們直接上代碼:spa
private int knapsackRecursive(Integer[][] dp, int[] profits, int[] weights, int capacity, int currentIndex) {
if (capacity <= 0 || profits.length == 0 || weights.length != profits.length ||
currentIndex >= profits.length)
return 0;
// 檢查咱們以前有木有遇到過一樣的子問題,有就直接返回結果
if (dp[currentIndex][capacity] == null) {
// 作完選擇以後繼續遞歸處理,注意選擇後咱們還能夠繼續選擇當前元素
int profit1 = 0;
if (weights[currentIndex] <= capacity)
profit1 = profits[currentIndex] + knapsackRecursive(dp, profits, weights,
capacity - weights[currentIndex], currentIndex);
// 跳過當前元素直接進行下一次遞歸
int profit2 = knapsackRecursive(dp, profits, weights, capacity, currentIndex + 1);
dp[currentIndex][capacity] = Math.max(profit1, profit2);
}
return dp[currentIndex][capacity];
}
複製代碼
這時候由於咱們把子問題的結果都緩存在二維數組中,因此咱們最多進行了NC次計算,因此咱們的時間複雜度降低到了O(NC),可是如今想必你們也都能發現都發覺了一般光緩存是達不到最優的,那咱們再來試試從另外一個方向,採用自下而上的方式來思考這個問題。(又到了激動人心的環節了!)code
本質上,咱們仍是想在上面的遞歸過程當中,對於每個索引,每個剩餘的可容重量,咱們都想在這一步得到能夠的最大收益。咱們仍是面臨兩個選擇,遞歸
dp[index-1][c]
。profit[index] + dp[index][c-weight[index]]
。最後咱們獲得了想得到最大收益的公式:dp[index][c] = max (dp[index-1][c], profit[index] + dp[index][c-weight[index]])
。跟咱們昨天的思路簡直一毛同樣!
剛看完昨天文章的你們確定明白是怎麼回事了,我也很少說了,直接把代碼貼出來供你們觀賞:
public int solveKnapsack(int[] profits, int[] weights, int capacity) {
if (capacity <= 0 || profits.length == 0 || weights.length != profits.length)
return 0;
int n = profits.length;
int[][] dp = new int[n][capacity + 1];
// 0載重量0收益
for (int i = 0; i < n; i++)
dp[i][0] = 0;
// 循環處理全部元素全部重量
for (int i = 0; i < n; i++) {
for (int c = 1; c <= capacity; c++) {
int profit1 = 0, profit2 = 0;
if (weights[i] <= c)
profit1 = profits[i] + dp[i][c - weights[i]];
if (i > 0)
profit2 = dp[i - 1][c];
dp[i][c] = profit1 > profit2 ? profit1 : profit2;
}
}
// 最大收益確定出如今最右下角
return dp[n - 1][capacity];
}
複製代碼
發現沒有,這個問題對咱們根本毫無壓力?掌握了昨天的進階文章,咱們甚至還能夠對這個算法再進行優化兩百遍!(其實兩遍)
皮這一下真開心,最後的優化我就不帶你們一塊兒走了,思路都是同樣的,留給你們去思考,你們平時作算法題的時候必定要多思考,盡力把題目轉化成咱們熟悉的題目,轉換成功後那咱們結題呀優化呀一切都遊刃有餘了。