等积子集的划分方案

等积子集的划分方案

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;
    }
}
posted @ 2025-06-01 15:42  Fanny123  阅读(249)  评论(0)    收藏  举报