来自专栏《LeetCode高频面试题》 欢迎订阅 |
目录
-
-
- 1、题目
- 2、思路
- 3、二维c++代码
- 4、二维java代码
- 5、一维优化
- 6、一维c++代码
- 7、一维java代码
-
1、题目
给你一个 只包含正整数 的 非空 数组
nums
。请你判断是否可以将这个数组分割成两个子集,使得两个子集的元素和相等。示例 1:
输入:nums = [1,5,11,5]
输出:true
解释:数组可以分割成 [1, 5, 5] 和 [11] 。
示例 2:
输入:nums = [1,2,3,5]
输出:false
解释:数组不能分割成两个元素和相等的子集。
提示:
1 <= nums.length <= 200
1 <= nums[i] <= 100
(动态规划)O ( n ? m ) O(n * m) O(n?m)
给定一个只包含正整数的非空数组
nums[0]
,判断是否可以从数组中选出一些数字,使得这些数字的和恰好等于整个数组的元素和的一半。样例:
文章图片
如样例所示,
nums = [1,5,11,5]
,数组可以分割成 [1, 5, 5]
和 [11]
,因此返回ture
。从题意来看,这个问题可以转换成
0-1
背包问题,如何看出来的? 我们不妨将换种表述方式:将大小为
n
的数组看成n
件物品,数组元素和sum
的一半看成一个容量为sum / 2
的背包, 每件物品只能使用一次,每件物品的体积是nums[i]
,求解是否可以选出一些物品,使得这些物品的总体积恰好为背包的容量,因此可以使用动态规划求解,下面我们来讲解具体做法。首先,如果
sum
是奇数,则不可能将数组分割成元素和相等的两个子集,因此直接返回false
。接下来我们去定义状态表示和推导状态转移方程。【LeetCode高频面试题|LeetCode 416. 分割等和子集 【c++/java详细题解】】状态表示:
f[i][j]
表示从前i
个数中选若干个数,是否使得这些数字的和恰好等于j
。因此f[i][j]
有两种状态,true
或者false
。状态计算:
假定
nums[]
数组下标从1
开始,如何确定f[i][j]
的值?一般去考虑最后一步,那么对于当前的数字
nums[i]
,可以选取也可以不选取:- 1、不选
nums[i]
,那么我们就从前i - 1
个数中选,看是否使得这些数字的和恰好等于j
,即f[i][j] = f[i - 1][j]
。 - 2、选择
nums[i]
,在背包可以装下的情况下,那么相应的背包容量就要减去nums[i]
,f[i][j]
的状态就可以从f[i - 1][j - nums[i]]
转移过来,即f[i][j] = f[i - 1][j - nums[i]]
。
true
,f[i][j]
就为true
。因此状态转移方程为f[i][j] = f[i - 1][j] | f[i - 1][j - nums[i]]
。初始化:
f[0][0] = true
:在前0
个数中,我们可以一个数都不去选,因此从前0
个数中选,使得这些数字的和恰好等于0
的状态为true
,其余的状态都初始化为false
。实现细节:
在推导状态转移方程时,我们假设的
nums[]
数组下标是从1
开始的,而实际中的nums[]
数组下标是从0
开始的,因此在代码的编写过程中,我们需要将所有nums[i]
的下标减去 1
,与使用的语言保持一致。时间复杂度分析:O ( n ? m ) O(n * m) O(n?m),
n
是nums
数组的大小,m
数组元素和的一半。空间复杂度分析: O ( n ? m ) O(n * m) O(n?m)
3、二维c++代码
class Solution {
public:
bool canPartition(vector& nums) {
int n = nums.size(), sum = 0;
for(int x : nums) sum += x;
if(sum % 2) return false;
int m = sum / 2;
vector> f(n + 1, vector(m + 1, false));
f[0][0] = true;
for(int i = 1;
i <= n;
i++){
for(int j = 1;
j <= m;
j++){
if(j >= nums[i - 1]) f[i][j] = f[i - 1][j - nums[i - 1]] | f[i - 1][j];
else f[i][j] = f[i - 1][j];
}
}
return f[n][m];
}
};
4、二维java代码
class Solution {
public boolean canPartition(int[] nums) {
int n = nums.length, sum = 0;
for(int x : nums) sum += x;
if(sum % 2 != 0) return false;
int m = sum / 2;
boolean[][] f = new boolean[n + 1][m + 1];
f[0][0] = true;
for(int i = 1;
i <= n;
i++){
for(int j = 1;
j <= m;
j++){
if(j >= nums[i - 1]) f[i][j] = f[i - 1][j - nums[i - 1]] || f[i - 1][j];
else f[i][j] = f[i - 1][j];
}
}
return f[n][m];
}
}
5、一维优化
我们可以发现,在计算
f[i][j]
的过程中,每一行f[i][j]
的值只与上一行的f[i - 1][j]
有关,因此考虑去掉前一维,则状态转移方程为:f[j] = f[j] | f[j - nums[i]]
。如果此时我们继续考虑第二层循环
j
从小往大计算,即:for (int i = 1;
i <= n;
i++){//为了下标对应,实际nums[i]应取nums[i - 1]
for (int j = nums[i];
j <= m;
j++){
f[j] = f[i] | f[j - nums[i]];
}
}
此时的状态便与二维的状态不等价了,因为在计算第
i
层的状态时,我们从小到大枚举, j - nums[i]
严格小于j
,那么f[ j- nums[i]]
一定会先于f[j]
被计算出来,于是我们计算出来的f[j - nums[i]]
仍为第i
层状态,这样f[j - nums[i]]
等价于f[i][j-nums[i]]
,实际上f[j - nums[i]]
应该等价于f[i - 1][j - nums[i]]
。为了解决这个问题只需要将
j
从大到小枚举。for (int i = 1;
i <= n;
i++){//为了下标对应,实际nums[i]应取nums[i - 1]
for (int j = m;
j >= nums[i];
j -- ){
f[j] = f[i] | f[j - nums[i]];
}
}
因为我们从大到小枚举
j
,而 j - nums[i]
严格小于j
,于是我们在计算f[j]
的时候f[j - nums[i]]
还未被第i
层状态更新过,那么它存的就是上一层(i - 1
层)的状态,即等价于f[i - 1][j - nums[i]]
。空间复杂度分析: O ( n ) O(n) O(n)
6、一维c++代码
class Solution {
public:
bool canPartition(vector& nums) {
int n = nums.size(), m = 0;
for (int x: nums) m += x;
if (m % 2) return false;
m /= 2;
vector f(m + 1);
f[0] = true;
for (int i = 1;
i <= n;
i++)
for (int j = m;
j >= nums[i - 1];
j -- )
f[j] = f[j] | f[j - nums[i - 1]];
return f[m];
}
};
7、一维java代码
class Solution {
public boolean canPartition(int[] nums) {
int n = nums.length, m = 0;
for (int x: nums) m += x;
if (m % 2 != 0) return false;
m /= 2;
boolean[] f = new boolean[m + 1];
f[0] = true;
for (int i = 1;
i <= n;
i++)
for (int j = m;
j >= nums[i - 1];
j -- )
f[j] |= f[j - nums[i - 1]];
return f[m];
}
}
如果我的文章对你有帮助的话,欢迎一键三连!!! |
文章图片
推荐阅读
- LeetCode高频面试题|剑指 Offer 59 - I. 滑动窗口的最大值 【c++/java详细题解】
- LeetCode高频面试题|LeetCode 213. 打家劫舍 II【c++/java详细题解】
- C++入门
- 数据结构与算法(c++)|【20. 滑动窗口】
- 数据结构与算法(c++)|【19. 单调栈】
- C|【c ++ primer 笔记】第4章 表达式
- C|【c ++ primer 笔记】第3章 字符串、向量和数组
- 数据结构与算法(c++)|【18. 模拟栈和队列】
- cpp|cpp 模板函数,类模板(1)