目录
[动态规划 - 完全背包](#动态规划 - 完全背包)
[518. 零钱兑换 II](#518. 零钱兑换 II)
[377. 组合总和 Ⅳ](#377. 组合总和 Ⅳ)
[518. 零钱兑换 II - 實作](#518. 零钱兑换 II - 實作)
[377. 组合总和 Ⅳ - 實作](#377. 组合总和 Ⅳ - 實作)
动态规划 - 完全背包
和01背包的差別
定義
<aside> 💡 01背包 → 每件物品只有一件,可以選擇取或不取 完全背包 → 每件物品有無數件,可以選擇取或不取,可以重複取多次
</aside>
核心代碼
-
01 背包
背包從大到小 → 保證每個物品僅被添加一次
cppfor(int i = 0; i < weight.size(); i++) { for(int j = bagWeight; j >= weight[i]; j--) { dp[j] = max(dp[j], dp[j - weight[i]] + value[i]); } }
-
完全背包
因為完全背包物品可以添加多次,所以要從小到大
cppfor(int i = 0; i < weight.size(); i++) { for(int j = weight[i]; j <= bagWeight ; j++) { dp[j] = max(dp[j], dp[j - weight[i]] + value[i]); } }
遍歷順序
-
在01背包中,
當在考慮某個物品時,必須確保在加入這個物品之前,已經知道了沒有這個物品的情況下到某個容量的最大價值。
這就是為什麼在使用一維dp陣列時,需要首先遍歷物品,然後再遍歷容量的原因。
-
但在完全背包中,
既然物品可以被選取無限次,那麼考慮某個物品時不必限制只看一次。
換句話說,可以在考慮這個物品的同時也考慮其他所有的物品。
因此,不論先遍歷物品還是先遍歷容量,結果都是一樣的。
說明的最後部分提到"dp[j] 是根据 下标j之前所对应的dp[j]计算出来的"
意味著當我們計算dp[j](也就是容量為j時的最大價值)時,我們只需要確保考慮了所有容量小於j的情況。
這段說明的核心意義是:在完全背包問題中,我們計算一維dp陣列的方法相對較為靈活,而在01背包問題中,我們必須先考慮物品,然後再考慮容量。
總結
這裡說的都是單純的完全背包問題,所以for循環的順序是可以顛倒的
但在題目上,因為leetcode目前沒有純背包問題,所以題目如果稍有變化,就會體現在遍歷順序上
讀題
518. 零钱兑换 II
自己看到题目的第一想法
看完完全背包的題目後,我看這題就在試著用這個方式去解題
-
定義DP數組以及下標的含意
dp[j] : 目標為j 的組成方式有dp[j]種
-
遞推公式
dp[j] += dp[j - coins[i]] → dp[j] 等於 dp[j] + dp[j - cois[i]]種方式組成
可以理解為 dp[j] 為包含當下conis[i]的數值可以組成的方法数,dp[j - conis[i]] 為不包含當下的conis[i]的數值
看完代码随想录之后的想法
如果今天要求組合或求排列是不一樣的
組合沒有順序,排列有順序
在遍歷順序上就會有所差別,假設今天要求的是組合那就是先遍歷物品在遍歷背包
但如果是求排列,則是先遍歷背包在遍歷物品
透過兩種不同的順序,得出不同的結果
377. 组合总和 Ⅳ
自己看到题目的第一想法
看到這題我的一個想法就是,這個就是求排列,程式碼基本一致,只要遍歷順序顛倒過來就可以了。
518. 零钱兑换 II - 實作
思路
-
定義DP數組以及下標的含意
dp[j]: 組成金額為j 的方法有dp[j]種
-
遞推公式
求組合就跟求目標和一樣,有多少方式可以組合成我們要的數
dp[j] += dp[j - coins[i]];
-
根據遞推公式,確定DP數組如何初始化
dp[0] = 1
就像在目標和的章節一樣,現在要求的是組成0有多少種方法,那假設我甚麼都不選就至少有一種方法可以實現,所以初始化為1
而其他数都是0,因為在不取任何数的狀況下無法達到
-
確定遍歷順序
這是一個完全背包的應用問題
所以在求背包容量時,是由小到大,而非大到小,因為同樣的數可以重複取
但遍歷数組的先後就會有差
假設先遍歷物品在遍歷背包,每個物品只會在當下在各個背包被遍歷一次,這沒有問題,這是組合
但先遍歷背包在遍歷物品,每個物品會在不同的背包中因為順序不同,所以被設定為不同的組合,這是求排序
所以我們必須先遍歷物品在遍歷背包。
Code
cpp
class Solution {
public:
int change(int amount, vector<int>& coins) {
vector<int> dp (amount + 1, 0);
dp[0] = 1;
for(int i = 0; i < coins.size(); i++) {
for(int j = coins[i]; j <= amount; j++) {
dp[j] += dp[j - coins[i]];
}
}
return dp[amount];
}
};
377. 组合总和 Ⅳ - 實作
思路
-
定義DP數組以及下標的含意
dp[j]: 組合為j 的排列方法有dp[j]種
-
遞推公式
dp[j] += dp[j - coins[i]];
-
根據遞推公式,確定DP數組如何初始化
dp[0] = 1
-
確定遍歷順序
這是一個完全背包的排列應用問題
所以在求背包容量時,是由小到大,而非大到小,因為同樣的數可以重複取
先遍歷背包在遍歷物品,每個物品會在不同的背包中因為順序不同,所以被設定為不同的組合,這是求排序
Code
cpp
class Solution {
public:
int combinationSum4(vector<int>& nums, int target) {
vector<double> dp (target + 1, 0);
dp[0] = 1;
for(int j = 0; j <= target; j++) {
for(int i = 0; i < nums.size(); i++) {
if(j - nums[i] >= 0) dp[j] += dp[j - nums[i]];
}
}
return dp[target];
}
};
總結
自己实现过程中遇到哪些困难
今天看完完全背包後,第一部份提到的排列與組合的差異,在第二題馬上就用到了,所以主要前面都在理解完全背包的思路以及釐清排序跟組合的程式差異
今日收获,记录一下自己的学习时长
今天主要學習大約2hr,整體就是了解了完全背包的概念以及釐清排列與組合的差異。
相關資料
● 今日学习的文章链接和视频链接
完全背包
视频讲解:带你学透完全背包问题! 和 01背包有什么差别?遍历顺序上有什么讲究?_哔哩哔哩_bilibili
https://programmercarl.com/背包问题理论基础完全背包.html
- 零钱兑换 II
视频讲解:动态规划之完全背包,装满背包有多少种方法?组合与排列有讲究!| LeetCode:518.零钱兑换II_哔哩哔哩_bilibili
https://programmercarl.com/0518.零钱兑换II.html
- 组合总和 Ⅳ
视频讲解:动态规划之完全背包,装满背包有几种方法?求排列数?| LeetCode:377.组合总和IV_哔哩哔哩_bilibili