惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

D
Docker
F
Fortinet All Blogs
爱范儿
爱范儿
博客园 - Franky
MyScale Blog
MyScale Blog
罗磊的独立博客
博客园 - 三生石上(FineUI控件)
B
Blog
P
Proofpoint News Feed
IT之家
IT之家
宝玉的分享
宝玉的分享
D
DataBreaches.Net
S
SegmentFault 最新的问题
Microsoft Azure Blog
Microsoft Azure Blog
GbyAI
GbyAI
M
MIT News - Artificial intelligence
L
LangChain Blog
奇客Solidot–传递最新科技情报
奇客Solidot–传递最新科技情报
月光博客
月光博客
雷峰网
雷峰网
Stack Overflow Blog
Stack Overflow Blog
量子位
V
V2EX
OSCHINA 社区最新新闻
OSCHINA 社区最新新闻

博客园 - Fanny123

LeetCode最大数字范围的整数之和 LeetCode统计好子数组 LeetCode边界与内部和相等的稳定子数组 三段式数组II 变为活跃状态的最小时间 平衡装运的最大数量 三段式数组 I 相邻字符串之间的最长公共前缀 分割字符串 找出数组中的所有 K 近邻下标 使叶子路径成本相等的最小增量 硬币面值还原 检查元素频次是否为质数 统计一个数组中好对子的数目 LeetCode 1482. 制作 m 束花所需的最少天数 C# 基础(更新中) 圆形靶内的最大飞镖数量 丑数 验证栈序列 BST的中序后继
等积子集的划分方案
Fanny123 · 2025-06-01 · via 博客园 - Fanny123

等积子集的划分方案

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;
    }
}