LeetCode 416. 分割等和子集

1 题目描述

给你一个只包含正整数非空数组nums。请你判断是否可以将这个数组分割成两个子集,使得两个子集的元素和相等。

1.1 输入测试

示例 1:

输入:nums = [1,5,11,5]

输出:true

解释:数组可以分割成 [1, 5, 5] 和 [11] 。

示例 2:

输入:nums = [1,2,3,5]

输出:false

解释:数组不能分割成两个元素和相等的子集。

1.2 数据范围

1 <= nums.length <= 200

1 <= nums[i] <= 100


2 解题思路

将数组分割成两个元素和相等的子集,意味着这两个子集的元素和都是总元素和sum的一半。

所以,道题的目标就是判断是否存在元素和为sum/2的子集。

其中包含的一个特殊情况是,当sum为奇数时,结果明显是不存在的。


2.1 DFS枚举

初看数据量比较小,于是就尝试DFS暴力枚举,最后果不其然超时了。

class Solution {
public:
bool canPartition(vector<int>& nums) {
int tar = 0; for (auto &num : nums)
{
tar += num;
} if (tar & 1)
{
return false;
}
return dfs(nums, 0, 0, tar >> 1);
} bool dfs(vector<int>& nums, int i, int sum, int tar)
{
if (sum == tar)
{
return true;
}
if (sum > tar)
{
return false;
} return i == nums.size() ? false : dfs(nums, i + 1, sum + nums[i], tar) || dfs(nums, i + 1, sum, tar);
}
};

2.2 记忆化搜索

既然暴力枚举不行,那就加上记忆化搜索,通过记录相同sum和i时的结果,达到减少重复计算的目的。

最终成功通过,运行时间只有55ms,但其实还有更优的解法

class Solution {
public:
bool canPartition(vector<int>& nums) {
int tar = 0; for (auto &num : nums)
{
tar += num;
} if (tar & 1)
{
return false;
}
return dfs(nums, 0, 0, tar >> 1);
} bool vis[10001][201]{};
bool mem[10001][201]{}; bool dfs(vector<int>& nums, int i, int sum, int tar)
{
if (sum == tar)
{
return true;
}
if (sum > tar)
{
return false;
} if (vis[sum][i])
{
return mem[sum][i];
} vis[sum][i] = true; return mem[sum][i] = i == nums.size() ? false : dfs(nums, i + 1, sum + nums[i], tar) || dfs(nums, i + 1, sum, tar);
}
};

2.3 DP

重新观察上面的计算过程就能发现,实际上我们所做的就是在枚举所有可能的子集和,这其实就是典型的0/1背包问题。

因此,我们可以采用动态规划的思想,构造一个初始只包含一个数字0的子集和数组v,接着依次从nums集合中取出一个数,与数组v中的子集和逐个相加得到一批新的子集和,并添加进子集和数组v。直到求得目标结果,或是所有数字都被取出。

这种写法的运行时间仅为15ms,但接下来我们还要介绍另一种DP的写法,为后面的bitset优化作铺垫。

class Solution {
public:
bool canPartition(vector<int>& nums) {
int tar = 0;
vector<int> v{0};
bool vis[10001]{}; for (auto &num : nums)
{
tar += num;
} if (tar & 1)
{
return false;
}
tar >>= 1; for (auto &num : nums)
{
for (int i = v.size() - 1; i >= 0; i--)
{
int n = num + v[i]; if (n == tar)
{
return true;
}
if (n > tar || vis[n])
{
continue;
}
vis[n] = true;
v.push_back(n);
}
} return false;
}
};

2.4 DP + bitset

在上面的DP写法中,我们使用了数组v记录已知的子集和,但也可以看出vis数组同时也记录了对应下标值的子集和是否存在。

因此,我们可以不使用v数组,而是通过遍历vis数组的方式来判断子集和是否存在。当然,根据0/1背包的特性,遍历的过程需要从后往前。

最终运行时间为23ms,比第一个DP方法稍慢,毕竟我们是遍历范围内所有可能的下标,而不像之前有额外的记录可以快速访问,但这么做也能相应地减少内存消耗。

不过这并不是我们的最终目标。

class Solution {
public:
bool canPartition(vector<int>& nums) {
int tar = 0;
bool vis[10001]{1}; for (auto &num : nums)
{
tar += num;
} if (tar & 1)
{
return false;
}
tar >>= 1; for (auto &num : nums)
{
for (int i = tar; i >= num; i--)
{
// 等价于 vis[i] |= vis[i - num];
// if (vis[i - num])
// {
// vis[i] = true;
// }
vis[i] |= vis[i - num];
}
} return vis[tar];
}
};

其实从上面的内层for循环中可以看出,我们每次所做的操作都是将第i位元素与第i偏移num位的元素进行或运算。所以,我们可以把内层循环的结果等价为,对整个vis数组做移位或运算,也就是vis |= vis << num

但即使我们知道这样的等价操作,也没办法直接对整个bool数组进行移位,这时候就需要用到支持<<>>位移操作的bitset容器来实现了。

其使用方法也很简单,只需要将原来的bool vis[N]替换成bitset<N> vis即可。

于是,我们的运行时间成功缩短到了5ms。对比第一种DP算法,这种方法在减少内存占用的同时也缩短了运行时间。

class Solution {
public:
bool canPartition(vector<int>& nums) {
int tar = 0;
bitset<10001> vis{1}; for (auto &num : nums)
{
tar += num;
} if (tar & 1)
{
return false;
}
tar >>= 1; for (auto &num : nums)
{
vis |= vis << num;
} return vis[tar];
}
};

其实到了这一步,我们还能将代码逻辑进行进一步的合并。将tar的计算合并到vis的for循环中,并在结尾统一对tar和vis的结果进行判断。

最终版本如下所示

class Solution {
public:
bool canPartition(vector<int>& nums) {
int tar = 0;
bitset<10001> vis{1}; for (auto &num : nums)
{
tar += num;
vis |= vis << num;
} return !(tar & 1) && vis[tar >> 1];
}
};

本文发布于2024年3月26日

最后编辑于2024年3月26日

LeetCode 416. 分割等和子集(bitset优化)的更多相关文章

  1. Leetcode 416分割等和子集

    416. 分割等和子集 已知是个背包问题,由于可以等分为两部分,所以必定是个偶数. 一开始想到的是回溯法 bool helper(vector<int>&nums, int i, ...

  2. Java实现 LeetCode 416 分割等和子集

    416. 分割等和子集 给定一个只包含正整数的非空数组.是否可以将这个数组分割成两个子集,使得两个子集的元素和相等. 注意: 每个数组中的元素不会超过 100 数组的大小不会超过 200 示例 1: ...

  3. Leetcode 416.分割等和子集

    分割等和子集 给定一个只包含正整数的非空数组.是否可以将这个数组分割成两个子集,使得两个子集的元素和相等. 注意: 每个数组中的元素不会超过 100 数组的大小不会超过 200 示例 1: 输入: [ ...

  4. [LeetCode]494. 目标和、416. 分割等和子集(0-1背包,DP)

    题目一 494. 目标和 给定一个非负整数数组,a1, a2, ..., an, 和一个目标数,S.现在你有两个符号 + 和 -.对于数组中的任意一个整数,你都可以从 + 或 -中选择一个符号添加在前 ...

  5. 【LeetCode动态规划#06】分割等和子集(01背包问题一维写法实战)

    分割等和子集 分割等和子集 给你一个 只包含正整数 的 非空 数组 nums .请你判断是否可以将这个数组分割成两个子集,使得两个子集的元素和相等. 示例 1: 输入:nums = [1,5,11,5 ...

  6. AtCoder Regular Contest 070 D - No Need 想法:利用单调性二分+bitset优化

    /** 题目:D - No Need 链接:http://arc070.contest.atcoder.jp/tasks/arc070_b 题意:给出N个数,从中选出一个子集,若子集和大于等于K,则这 ...

  7. 洛谷 P5527 - [Ynoi2012] NOIP2016 人生巅峰(抽屉原理+bitset 优化背包)

    洛谷题面传送门 一道挺有意思的题,想到了某一步就很简单,想不到就很毒瘤( 首先看到这样的设问我们显然可以想到背包,具体来说题目等价于对于每个满足 \(i\in[l,r]\) 的 \(a_i\) 赋上一 ...

  8. hdu 5506 GT and set dfs+bitset优化

    GT and set Time Limit: 2000/1000 MS (Java/Others)    Memory Limit: 65536/65536 K (Java/Others) Probl ...

  9. hdu 5745 La Vie en rose DP + bitset优化

    http://acm.hdu.edu.cn/showproblem.php?pid=5745 这题好劲爆啊.dp容易想,但是要bitset优化,就想不到了. 先放一个tle的dp.复杂度O(n * m ...

  10. hdu_5036_Explosion(bitset优化传递闭包)

    题目链接:hdu_5036_Explosion 题意: 一个人要打开或者用炸弹砸开所有的门,每个门里面有一些钥匙,一个钥匙对应一个门,有了一个门的钥匙就能打开相应的门,告诉每个门里面有哪些门的钥匙,问 ...

随机推荐

  1. mysql安装及增删改查操作---day35

    # ### mysql ''' 命令可以用tab来补全 d: D:\>cd MySQL5.7 D:\>cd D:\MySQL5.7\mysql-5.7.25-winx64\bin 直接切换 ...

  2. 1.Go 的基本数据类型

    Go 的基本数据类型

  3. 谈一谈如何使用etcd中的事务

    本文内容来源于自己学习时所做的记录,主要来源于文章最后的参考链接,如有侵权,请联系删除,谢谢! etcd 是一个 key/value 类型的数据库.既然我们需要存储数据,必然会面临这样一个需求,即希望 ...

  4. 【Azure 应用服务】Azure Function 部署槽交换时,一不小心把预生产槽上的配置参数交换到生产槽上,引发生产错误

    问题描述 部署Function代码先到预生产槽中,进行测试后通过交换方式,把预生产槽中的代码交换到生产槽上,因为在预生产槽中的设置参数值与生产槽有不同,但是在交换的时候,没有仔细检查.导致在交换的时候 ...

  5. X86模拟龙芯与编译 .NET CoreCLR

    目录 .NET 收到一台龙芯机器 编译 CoreCLR 环境要求 部署虚拟机与环境 Linux 安装 KVM 下载需要的文件 启动模拟器 下载 CoreCLR 尝试编译 CoreCLR 前段时间得知龙 ...

  6. C/C++、C#、JAVA(三):字符串操作

    C/C++.C#.JAVA(三):字符串操作 目录 C/C++.C#.JAVA(三):字符串操作 定义字符串 C C++ C# JAVA 捕捉输入和输出 等值比较 C/C++ C# JAVA 字符串操 ...

  7. 一文带你了解 「图数据库」Nebula 的存储设计和思考

    本文首发于 Nebula Graph Community 公众号 在上次的 nebula-storage on nLive 直播中,来自 Nebula 存储团队的负责人王玉珏(四王)同大家分享了 ne ...

  8. 从0开始入门智能知识库和星火大模型,打造AI客服。

    介绍FastWiki FastWiki是一个高性能.基于最新技术栈的知识库系统,旨在为大规模信息检索和智能搜索提供解决方案.它采用微软Semantic Kernel进行深度学习和自然语言处理,在后端使 ...

  9. Codeforces Round 829 (Div. 1)A1. Make Nonzero Sum (easy version)(思维找规律)

    先考虑无解的情况:当n为奇数时无解 相邻的两个元素一定可以变成0 \[a[i] != a[i + 1]时, 分成[i, i], 和[i + 1, i + 1] \] \[a[i] = a[i + 1] ...

  10. WPF --- 触摸屏下的两个问题

    引言 本片文章分享一下之前遇到的WPF应用在触摸屏下使用时的两个问题. 场景 具体场景就是一个配置界面, ScrollViewer 中包含一个StackPanel 然后纵向堆叠,已滚动的方式查看,然后 ...