2020年9月19日 周六 天气晴 【不悲叹过去,不荒废现在,不惧怕未来】
1. 二维DP
两个关键点:
- 将问题转换为背包问题
- 准确书写状态转移方程
class Solution {
public:
bool canPartition(vector<int>& nums) {
if(nums.empty()) return true;
int sum = 0;
for(auto num:nums) sum += num;
// 和为奇数直接返回false
if(sum % 2 == 1) return false;
int n = nums.size(), m = sum / 2;
vector<vector<bool>> dp(n+1,vector<bool>(m+1,false));
// base
for(int i=1;i<n;++i) dp[i][0] = true;
for(int i=1;i<=n;++i){
for(int j=1;j<=m;++j){
if(j-nums[i-1]<0) dp[i][j] = false;
else dp[i][j] = dp[i-1][j] || dp[i-1][j-nums[i-1]];
// 由于状态转移方程的特殊性,dp[m]==true时可提前结束,可以认为是剪枝操作
if(dp[n][m]==true) return true;
}
}
return false;
}
};
- 时间复杂度: O ( n m ) O\left( {nm} \right) O(nm),这里 n n n为数组元素个数, m m m为数组元素和的一半
- 空间复杂度: O ( n m ) O\left( {nm} \right) O(nm)
2. 一维DP
两个关键点(具体看下面的代码注释):
- 二维改成一维后,对于容量(背包的)这一维要逆序遍历
- 掌握两种提前结束程序(剪枝)的方法
class Solution {
public:
bool canPartition(vector<int>& nums) {
if(nums.empty()) return true;
int sum = 0;
for(auto num:nums) sum += num;
// 和为奇数直接返回false
if(sum % 2 == 1) return false;
int n = nums.size(), m = sum / 2;
vector<bool> dp(m+1,false);
// base
dp[0] = true;
for(int i=1;i<=n;++i){
// 一维要逆序,因为少了一维i ,正序会导致一个物体被重复使用,逆序能避免这个问题。
for(int j=m;j>=1;--j){
// 逆序还有一个好处就是:j-nums[i-1]小于0时可提前结束循环,因为后面的j只会越来越小
if(j-nums[i-1]<0) {
dp[j] = false;
break;
}
else dp[j] = dp[j] || dp[j-nums[i-1]];
// 由于状态转移方程的特殊性,dp[m]==true时可提前结束程序,可以认为是剪枝操作
if(dp[m]==true) return true;
}
}
return false;
}
};
- 时间复杂度: O ( n m ) O\left( {nm} \right) O(nm),这里 n n n为数组元素个数, m m m为数组元素和的一半
- 空间复杂度: O ( m ) O\left( {m} \right) O(m)
参考文献
https://leetcode-cn.com/problems/partition-equal-subset-sum/solution/0-1-bei-bao-wen-ti-xiang-jie-zhen-dui-ben-ti-de-yo/
https://mp.weixin.qq.com/s?__biz=MzAxODQxMDM0Mw==&mid=2247485103&idx=1&sn=8a9752e18ed528e5c18d973dcd134260&chksm=9bd7f8a7aca071b14c736a30ef7b23b80914c676414b01f8269808ef28da48eb13e90a432fff&scene=178#rd