学算法好痛苦,完全是对我智力的一次次折磨,看了好多博客,对二维dp数组的理解都是直接搬了代码随想录,搬了随想录又没详细解释,大家都是一眼看懂的吗,好吧()
j的这样的背包——一个独立的,容量为j的背包。(没把它理解为“剩余容量”)0至i的物品里任意挑,挑几个无所谓,挑多重的无所谓dp[i][j]dp[n][m]就是一个n+1行、m+1列的表格。取其中的一格看,比如第i行第j列的dp[i][j],它的含义为:对于容量为j的背包,编号0-i的物品随便挑,背包最大价值为多少。下面,我们详细说一下dp[i][j]的递推公式。dp[i][j]=max{含第i个物品的挑选的背包价值,不含第i个物品的挑选的背包价值}
dp[i-1][j]相同。因为这种情况等于是从容量为j的背包里选,但只从第0到第i-1个物品里挑。dp[i][j]
=max{含第i个物品的挑选的背包价值,同样大小的背包不含第i个物品的挑选}
=max{dp[i-1][j],dp[i-1][j-weight[i]]+value[i]}

备注:①中还需要判断 j 是否小于 weight[i],不然j-weight[i]就变成负值,无法成为数组下标。
如不考虑中间状态,可优化空间复杂度,仅使用j+1个空间,而非(i+1)*(j+1)个。

需要逆序(遍历 j 时),以保证从后往前的时候前面的初始化是没动的,不然dp[j-weight[i]]的计算会用到前面已经加过value[i]的元素,从而再加一个。这个看代码随想录手动推一遍还是比较简单,故不细说。
由于看了之前的0-1背包,被困住了思路,老是觉得0-1背包是求的一堆元素的“最大值”,而等和子集求的是一堆元素等于某个值的情况,思想上感觉不一样。知道回溯法不配合剪枝肯定不行(200个num,
2
200
2^{200}
2200)
然后隔了一天看了别人没学代码随想录的题解,发现其实很简单,因为我误认为我这里的dp[][]里存放的是和,或者一个特定的值什么的,其实不是,就是单纯的布尔变量,1,或者0。dp[i][j]代表:i个元素,任意选择是否"可"为j?可->1,不可->0,就这么简单。
class Solution {
public:
bool canPartition(vector<int>& nums)
{
//元素和相等——>数字集合的和等于1/2 sum
if (accumulate(nums.begin(), nums.end(), 0) % 2 != 0 || nums.size() == 1)
return 0;
int target = accumulate(nums.begin(), nums.end(), 0) / 2;
vector <vector<int>> dp(nums.size(), vector<int>(target + 1,0));
//vector> vec(row, vector (col,0)); -- 用 0 来初始化二维vector
for (int i = 0; i < nums.size(); ++i) {
dp[i][0] = 1;//均有方法可使得和为0
}
for (int j = 1; j <= target; ++j) {
dp[0][j] = (j == nums[0]);
}
for (int i = 1; i < nums.size(); ++i)
{
for (int j = 1; j <= target; ++j)
{
dp[i][j] = dp[i - 1][j];
if (j - nums[i] >= 0)
dp[i][j] = dp[i - 1][j] || dp[i - 1][j - nums[i]];
}
if (dp[i][target] == 1)
return 1;
}
return 0;
}
};
第一,vector二维数组的初始化问题。如果不手动初始化的话,似乎vector只会初始化一行,所以需要使用vector这一行代码,前面是row的数量,后面是col的数量。
第二,dp的初始化。第一次写的时候,初始化很想当然,理清思路以后初始化还是比较简单的。