等积子集的划分方案
等积子集的划分方案
LeetCode 452 周赛2025-06-01
题目
给你一个整数数组 nums,其中包含的正整数 互不相同 ,另给你一个整数 target。
请判断是否可以将 nums 分成两个 非空、互不相交 的 子集 ,并且每个元素必须 恰好 属于 一个 子集,使得这两个子集中元素的乘积都等于 target。
如果存在这样的划分,返回 true;否则,返回 false。
子集 是数组中元素的一个选择集合。
示例 1:
输入: nums = [3,1,6,8,4], target = 24
输出: true
解释:子集 [3, 8] 和 [1, 6, 4] 的乘积均为 24。因此,输出为 true 。
示例 2:
输入: nums = [2,5,3,7], target = 15
输出: false
解释:无法将 nums 划分为两个非空的互不相交子集,使得它们的乘积均为 15。因此,输出为 false。
提示:
3 <= nums.length <= 12
1 <= target <= 1015
1 <= nums[i] <= 100
nums 中的所有元素互不相同。©leetcode
思路
首先所有元素的乘积要满足是target*target。在此基础上,找到包含第一个元素的集合,这个集合里所有元素乘积上target。实现上是动态规划dp,从前往后便遍历,有两种可能,包含当前元素(乘积✖️当前元素),或者不包含当前元素(也就是乘积不变),最后出口是当前乘积已经是target(即满足条件),或者走到数组最后(即不能满足条件)。
时间复杂度为2n, n 为数组长度。空间复杂度是2n。
代码
这个代码不算最优代码,dp的实现是递归,利用了一个map去避免重复计算。更优秀的写法是直接写数组实现的dp。
public class Solution {
public bool CheckEqualPartitions(int[] nums, long target) {
// all * to be target * target
long allP=1;
int n=nums.Length;
for(int i=0;i<n;i++){
allP*=nums[i];
}
if(allP!=target*target){
return false;
}
long curr=nums[0];
//recursively
bool res= CheckHelper(nums,curr,target,1);
return res;
}
//map
Dictionary<string,bool> results=new Dictionary<string,bool>();
private bool CheckHelper(int[] nums,long curr,long target,int idx){
string key=idx+","+curr;
if(results.ContainsKey(key)){
return results[key];
}
if(curr==target){
results.Add(key,true);
return true;
}
int n=nums.Length;
if(idx==n){
results.Add(key,false);
return false;
}
bool res=CheckHelper(nums,curr*nums[idx],target,idx+1) || CheckHelper(nums,curr,target,idx+1);
results.Add(key,res);
return res;
}
}
大佬们的解答
暴力分组+位运算
因为数组长度n最多只有12,212个可能,那可以直接暴力遍历去做分组。n位的二位数,当第i位是0代表第i个数字在第一组,否则在第二组。
时间复杂度为2n * n. 空间复杂度是常量级.
class Solution {
public:
bool checkEqualPartitions(vector<int>& nums, long long target) {
for (int mask = 0; mask < 1 << nums.size(); mask++) {
long long v1 = 1, v2 = 1;
for (int i = 0; i < nums.size(); i++) {
if (mask >> i & 1) {
v1 = min(target + 1, v1 * nums[i]);
} else {
v2 = min(target + 1, v2 * nums[i]);
}
}
if (v1 == target and v2 == target) return true;
}
return false;
}
};
这个解法在上面的解法的基础上,增加了剪枝:当某一个集合的乘积超过target,则可以进行剪枝。
class Solution {
public:
bool checkEqualPartitions(vector<int>& nums, long long target) {
int n = nums.size();
for (int i = 1; i < (1 << n); i++) {
long long p1 = 1, p2 = 1;
for (int j = 0; j < n; j++) {
if (i >> j & 1) p1 *= nums[j];
else p2 *= nums[j];
if (p1 > target || p2 > target) break;
}
if (p1 == target && p2 == target) return true;
}
return false;
}
};
DP数组+位运算
dp数组代表组合可能i对应的乘积,其中i的第j位代表是否包含nums[j]。
最终找到组合i以及对应的组合,能满足两个组合的乘积都是target。其中i对应的第二个组合是full ^ i,也就是每位都取反。
class Solution {
public boolean checkEqualPartitions(int[] nums, long target) {
int n = nums.length, full = ((1 << n) - 1);
long[] mul = new long[1 << n];
for (int i = 1; i < 1 << n; i++) {
long ans = 1;
for (int j = 0; j < n; j++) {
if ((i >> j & 1) != 0) {
ans = ans * nums[j];
}
}
mul[i] = ans;
}
for (int i = 1; i < full; i++) {
if (mul[i] == mul[full ^ i] && mul[i] == target) {
return true;
}
}
return false;
}
}