分割等和子集
此题来源于力扣(分割等和子集)
题目要求:给你一个 只包含正整数的非空数组nums 。请你判断是否可以将这个数组分割成两个子集,使得两个子集的元素和相等。
示例1:
输入:nums = [1,5,11,5]
输出:true
解释:数组可以分割成 [1, 5, 5] 和 [11] 。
示例 2:
输入:nums = [1,2,3,5]
输出:false
解释:数组不能分割成两个元素和相等的子集。
解题思路:(动态规划)
一. 转换为 「0 - 1」 背包问题
这道题可以换一种表述:给定一个只包含正整数的非空数组
n
u
m
s
[
0
]
nums[0]
nums[0],判断是否可以从数组中选出一些数字,使得这些数字的和等于整个数组的元素和的一半。因此这个问题可以转换成「0 - 1背包问题」。这道题与传统的「0 − 1 背包问题」的区别在于,传统的「0 − 1 背包问题」要求选取的物品的重量之和不能超过背包的总容量,这道题则要求选取的数字的和恰好等于整个数组的元素和的一半。类似于传统的「0 − 1 背包问题」,可以使用动态规划求解。即:
1:0-1 背包问题选取的物品的容积总量 不能超过 规定的总量。
2:本题选取的数字之和需要 恰好等于 规定的和的一半。
二. 在使用动态规划求解之前,首先需要进行以下判断
1:根据数组的长度
n
n
n 判断数组是否可以被划分。如果
n
<
2
n<2
n<2,则不可能将数组分割成元素和相等的两个子集,因此直接返回
f
a
l
s
e
false
false。
2:计算整个数组的元素和
s
u
m
sum
sum 以及最大元素
m
a
x
N
u
m
maxNum
maxNum。
①:如果
s
u
m
sum
sum 是奇数,则不可能将数组分割成元素和相等的两个子集,因此直接返回
f
a
l
s
e
false
false。
②:如果
s
u
m
sum
sum 是偶数,则令
t
a
r
g
e
t
=
s
u
m
2
target=\frac{sum}{2}
target=2sum ,需要判断是否可以从数组中选出一些数字,使得这些数字的和等于
t
a
r
g
e
t
target
target。如果
m
a
x
N
u
m
>
t
a
r
g
e
t
maxNum>target
maxNum>target,则除了
m
a
x
N
u
m
maxNum
maxNum 以外的所有元素之和一定小于
t
a
r
g
e
t
target
target,因此不可能将数组分割成元素和相等的两个子集,直接返回
f
a
l
s
e
false
false。
三. 初始化状态表
创建二维数组
d
p
dp
dp,包含
n
n
n 行
t
a
r
g
e
t
+
1
target+1
target+1 列,其中
d
p
[
i
]
[
j
]
dp[i][j]
dp[i][j]表示从数组的
[
0
,
i
]
[0,i]
[0,i] 下标范围内选取若干个正整数(可以是0个),是否存在一种选取方案使得被选取的正整数的和等于
j
j
j。初始时,
d
p
dp
dp 中的全部元素都是
f
a
l
s
e
false
false。
四. 边界情况的考虑
1:如果不选取任何正整数,则被选取的正整数等于0。因此对于所有
0
≤
i
<
n
0≤i<n
0≤i<n,都有
d
p
[
i
]
[
0
]
=
t
r
u
e
dp[i][0]=true
dp[i][0]=true。
2:当
i
=
=
0
i==0
i==0 时,只有一个正整数
n
u
m
s
[
0
]
nums[0]
nums[0] 可以被选取,因此
d
p
[
0
]
[
n
u
m
s
[
0
]
]
=
t
r
u
e
dp[0][nums[0]]=true
dp[0][nums[0]]=true。
五. 状态的转移
对于
i
>
0
i>0
i>0 且
j
>
0
j>0
j>0 的情况,如何确定
d
p
[
i
]
[
j
]
dp[i][j]
dp[i][j] 的值?需要分别考虑以下两种情况。
1:如果
j
≥
n
u
m
s
[
i
]
j≥nums[i]
j≥nums[i],则对于当前的数字
n
u
m
s
[
i
]
nums[i]
nums[i],可以选取也可以不选取,两种情况只要有一个为
t
r
u
e
true
true,就有
d
p
[
i
]
[
j
]
=
t
r
u
e
dp[i][j]=true
dp[i][j]=true。
①:如果不选取
[
i
]
n
u
m
s
[
i
]
[i]nums[i]
[i]nums[i],则
d
p
[
i
]
[
j
]
=
d
p
[
i
−
1
]
[
j
]
dp[i][j]=dp[i−1][j]
dp[i][j]=dp[i−1][j];
②:如果选取
[
i
]
n
u
m
s
[
i
]
[i]nums[i]
[i]nums[i],则
d
p
[
i
]
[
j
]
=
d
p
[
i
−
1
]
[
j
−
n
u
m
s
[
i
]
]
dp[i][j]=dp[i−1][j−nums[i]]
dp[i][j]=dp[i−1][j−nums[i]]。
2:如果
j
<
n
u
m
s
[
i
]
j<nums[i]
j<nums[i],则在选取的数字的和等于
j
j
j 的情况下无法选取当前的数字
n
u
m
s
[
i
]
nums[i]
nums[i],因此有
d
p
[
i
]
[
j
]
=
d
p
[
i
−
1
]
[
j
]
dp[i][j]=dp[i−1][j]
dp[i][j]=dp[i−1][j]。
状态转移方程如下:
d
p
[
i
]
[
j
]
=
{
d
p
[
i
−
1
]
[
j
]
∣
d
p
[
i
−
1
]
[
j
−
n
u
m
s
[
i
]
]
,
j
≥
n
u
m
s
[
i
]
d
p
[
i
−
1
]
[
j
]
,
j
<
n
u
m
s
[
i
]
dp[i][j]= \left \{\begin{array}{cc} dp[i−1][j] \ |\ dp[i−1][j−nums[i]], &j≥nums[i]\\ dp[i−1][j], & j<nums[i] \end{array}\right.
dp[i][j]={dp[i−1][j] ∣ dp[i−1][j−nums[i]],dp[i−1][j],j≥nums[i]j<nums[i]
最终得到
d
p
[
n
−
1
]
[
t
a
r
g
e
t
]
dp[n−1][target]
dp[n−1][target] 即为答案。
代码(c++):
class Solution {
public:
bool canPartition(vector<int>& nums) {
int n = nums.size();
if (n < 2) {
return false;
}
int sum = accumulate(nums.begin(), nums.end(), 0);
int maxNum = *max_element(nums.begin(), nums.end());
if (sum & 1) {
return false;
}
int target = sum / 2;
if (maxNum > target) {
return false;
}
vector<vector<int>> dp(n, vector<int>(target + 1, 0));
for (int i = 0; i < n; i++) {
dp[i][0] = true;
}
dp[0][nums[0]] = true;
for (int i = 1; i < n; i++) {
int num = nums[i];
for (int j = 1; j <= target; j++) {
if (j >= num) {
dp[i][j] = dp[i - 1][j] | dp[i - 1][j - num];
} else {
dp[i][j] = dp[i - 1][j];
}
}
}
return dp[n - 1][target];
}
};