揹包問題:
最經典的0-1揹包
揹包問題是有一個一定容量的揹包,然後有一堆物品,這些物品有一定的重量,要求在不超過揹包容量的情況下,裝最多的重量的問題。
揹包問題的變體
揹包問題有很多的變體,主要有以下幾個:
- 物品不僅僅有重量還會對應不同的價值,求價值最高,而不是重量最高
- 每個物品有無限個而不是隻有一個
- 不是求最大重量,而是求達到重量的組合的個數
揹包問題的解法:
揹包問題是用動態規劃的方法解決,可以構造一個二維動規數組解決問題,而二維數組的做法可以進一步優化空間複雜度變爲一維的動規數組。
動態規劃的問題主要要解決三個問題,1、dp數組的設計,2、邊界情況,3、遞推公式。
二維動規劃數組的解法:
- 動態規劃數組設置:設置大小爲dp[items.length + 1][backpackSize + 1]的boolean數組,其中數組中每個元素dp[i][j]表示只用前i個物品,能否達到j的負重。+1是用於設置邊界條件。
- 邊界情況:邊界條件是在考慮0件物品 和 揹包負重爲0 時最大負重爲0。
- 遞推公式:
- dp[i][j] = dp[i - 1][j] 只要用前i-1個物品能達到 j 重量,那用上物品i,也能達到j重量
- j >= items[i-1] && dp[i-1][j - items[i-1]] 時,dp[i][j] 設置爲true
保證揹包的負重大於物品i(這裏-1是因爲0用於設置邊界,從1開始),證明當前負重能放下物品i,如果在不用i物品時,可以達到負重j減物品i的重量,那麼用上了物品i,就可以達到j的重量。
- 具體的解法看下題 lintcode 92
一維度數組:
也是考慮動規數組的設計、邊界情況、遞推公式三個問題,二維數組優化爲一維,是滾動重複運用了一維數組,達到二維的效果,它們都要兩重循環,時間是一樣的。
- 動規數組:設置大小爲dp[backpackSize + 1]的boolean數組,dp[j]表示:能否達到j負重
- 邊界情況:dp[0] = 0
- 遞推公式:同樣是遍歷各個物體i,在只考慮i-1的物品時,如果能達到dp[j - items[i]],那麼考慮了物品i,可以達到負重j。
這裏注意,因爲每個物品只能用一次,所以應該從最大負重往前遞推,如果從前往後遞推,物品就可能被使用了多次。 - 同樣結合lintcode92 看完整的解法
揹包變體的解法:
- 帶價值的物品:遞推數組不要設置爲boolean,設置爲int數組,表示價值
- 二維:dp[i][j]表示,只考慮前i的物品,負重不超過j的情況下,價值最高的值
- 一維:dp[j] 表示:不超過負重j的情況下,價值最高的值得
- 結合lintcode 125 看完整解法
- 物品不是隻有一個,而是有無限個:只需要把遞推的方向反轉,剛剛提到最大負重往前遞推,防止多次使用到物體,只要正向遞推,就可以解決無限個物體的問題,具體看lintcode 440 完整解法
- 求組合個數:只要把動規數組的值,設置爲達到這個重量的組合個數,邊界情況dp[0]=1 即可,具體看 lintcode 563
lintcode 92 揹包問題
這是經典的01揹包問題:
二維數組的動態規劃解法:
public int backPack(int backpackSize, int[] items) {
// 由於要從揹包爲0,物體數爲0的狀態開始,所以dp數組要多一行的維度
boolean dp[][] = new boolean[items.length + 1][backpackSize + 1];
for (int i = 0; i <= items.length; i++) {
for (int j = 0; j <= backpackSize; j++) {
dp[i][j] = false;
}
}
// dp[i][j] 表示 前i個揹包,隨機選若干個,能不能達到j的重量
dp[0][0] = true;
for (int i = 1; i <= items.length; i++) {
for (int j = 0; j <= backpackSize; j++) {
dp[i][j] = dp[i - 1][j];
if (j >= items[i-1] && dp[i-1][j - items[i-1]]) {
dp[i][j] = true;
}
}
}
for (int i = backpackSize; i >= 0; i--) {
if (dp[items.length][i]) {
return i;
}
}
return 0;
}
一維滾動數組解法:
public int backPack(int backpackSize, int[] items) {
// dp[i]代表,容量爲 i 的揹包,最否能裝到負重j
boolean dp[] = new boolean[backpackSize + 1];
dp[0] = true;
for(int i = 0; i < items.length; i++) {
for(int j = backpackSize; j >= items[i]; j--) {
if(dp[j - items[i]]){
dp[j] = true;
}
}
}
for(int j = backpackSize; j >= 0; j--){
if(dp[j]){
return j;
}
}
return 0;
}
lintcode 125 帶價值的揹包問題
這是01揹包中物品帶有價值的變體
只要把01揹包問題中,dp數組的值設置爲當前最大價值,而非boolean能否得到。稍微改動即可
二維數組解法:
public int backPackII(int backpackSize, int[] items, int[] itemsValues) {
// write your code here
int[][] dp = new int[items.length + 1][backpackSize + 1];
for(int i = 0; i <= items.length; i++){
for(int j = 0; j <= backpackSize; j++){
if(i == 0 || j == 0){
dp[i][j] = 0;
}else if(items[i-1] > j){
dp[i][j] = dp[i-1][j];
}else{
dp[i][j] = Math.max(dp[i-1][j], dp[i-1][j-items[i-1]] + itemsValues[i-1]);
}
}
}
return dp[items.length][backpackSize];
}
一維滾動數組:
public int backPackII2(int backpackSize, int[] items, int[] itemsValues) {
// write your code here
int[] dp = new int[backpackSize + 1];
for(int i = 0; i < items.length; i++){
for(int j = backpackSize; j >= items[i]; j--){
if(dp[j] < dp[j - items[i]] + itemsValues[i]){
dp[j] = dp[j - items[i]] + itemsValues[i];
}
}
}
return dp[backpackSize];
}
lintcode 440. 揹包問題 III
這是01揹包問題物體可以用無限次,且帶有價值的變體
dp數組爲當前最大價值,遞推公式從前往後遞推,無限次使用物品即可。
一維滾動數組
public int backPackIII(int[] items, int[] itemsValues, int backpackSize) {
int[] dp = new int[backpackSize + 1];
for(int i = 0; i < items.length; i++){
for(int j = items[i]; j <= backpackSize; j++){
if(dp[j] < dp[j - items[i]] + itemsValues[i]){
dp[j] = dp[j - items[i]] + itemsValues[i];
}
}
}
return dp[backpackSize];
}
lintcode 563. 揹包問題 V
這是求達到target負重的組合個數,物品只能用一次
只要邊界情況設置爲1,然後把dp的值,變爲當前的組合個數,遞推時不是是否能達到的boolean,而是累加上前狀態的個數即可。
public int backPackV(int[] nums, int target) {
// Write your code here
int[] dp = new int[target + 1];
dp[0] = 1;
for (int i = 0; i < nums.length; ++i)
for (int j = target; j >= nums[i]; --j)
dp[j] += dp[j - nums[i]];
return dp[target];
}
lintcode 562. 揹包問題 IV
這是求達到target負重的組合個數,物品只能用無限次
只要只能用一次的稍微修改,改爲從前向後遞推即可。
public int backPackIV(int[] nums, int target) {
// write your code here
int[] dp = new int[target + 1];
dp[0] = 1;
for(int i = 0; i < nums.length; i++){
for(int j = nums[i]; j <= target; j++){
dp[j] += dp[j - nums[i]];
}
}
return dp[target];
}