当前位置: 首页 > news >正文

【力扣100题】49.分割等和子集

题目描述

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

示例:

  • 输入:nums = [1,5,11,5]→ 输出:true(数组可以分割成[1, 5, 5][11]
  • 输入:nums = [1,2,3,5]→ 输出:false(数组不能分割成两个元素和相等的子集)

解题思路总览

方法思路时间复杂度空间复杂度
动态规划(0-1背包)转化为是否能从数组中选取若干元素使其和等于 sum/2O(n × sum/2)O(sum/2)
DFS + 剪枝从数组中尝试选取元素,看能否凑到目标值O(2^n)O(n)
位运算优化用 bitset 优化空间,适合 sum 较小的情况O(n × sum/32)O(sum/32)

本题采用**动态规划(0-1背包)**方法。


核心思路转化

将原问题转化为:是否可以从数组中选取若干元素,使得它们的和等于 sum/2?

  • 如果能找到一个子集的和为 sum/2,那么剩下的元素和也是 sum/2,问题得解
  • 这就是一个「0-1背包」问题:每个数只能用一次,问能否装满容量为 sum/2 的背包

完整代码

classSolution{public:boolcanPartition(vector<int>&nums){intsum=0;for(inti=0;i<nums.size();i++)sum+=nums[i];if(sum%2==1)returnfalse;vector<int>dp(10001,0);for(inti=0;i<nums.size();i++){for(intj=sum/2;j>=nums[i];j--){dp[j]=max(dp[j],dp[j-nums[i]]+nums[i]);}}if(dp[sum/2]==sum/2)returntrue;returnfalse;}};

算法流程图

输入: nums = [1, 5, 11, 5] 计算总和: sum = 1 + 5 + 11 + 5 = 22 sum % 2 == 0? 是 target = sum / 2 = 11 初始化: dp[0...11] = 0 容量为 11 的背包 遍历数组: i = 0, nums[0] = 1: j = 11: j >= 1, dp[11] = max(dp[11], dp[10]+1) = 1 j = 10: j >= 1, dp[10] = max(dp[10], dp[9]+1) = 1 ... j = 1: j >= 1, dp[1] = max(dp[1], dp[0]+1) = 1 dp[1] = 1 i = 1, nums[1] = 5: j = 11: j >= 5, dp[11] = max(dp[11], dp[6]+5) = 6 j = 10: j >= 5, dp[10] = max(dp[10], dp[5]+5) = 6 ... j = 5: j >= 5, dp[5] = max(dp[5], dp[0]+5) = 5 dp[5] = 5 i = 2, nums[2] = 11: j = 11: j >= 11, dp[11] = max(dp[11], dp[0]+11) = 11 dp[11] = 11 i = 3, nums[3] = 5: j = 11: j >= 5, dp[11] = max(dp[11], dp[6]+5) = 11 (不更新) ... 最终 dp[11] = 11 dp[11] == target == 11? 是 返回 true

逐行解析

intsum=0;for(inti=0;i<nums.size();i++)sum+=nums[i];

含义:计算数组所有元素的总和。

if(sum%2==1)returnfalse;

含义:如果总和是奇数,无法平分成两个和相等的子集,直接返回 false。

vector<int>dp(10001,0);

含义:创建背包容量数组。dp[j]表示容量为j的背包最多能装多少重量的物品(元素和)。这里dp大小设为 10001 是因为sum <= 200 × 100 = 20000,所以sum/2 <= 10000

for(inti=0;i<nums.size();i++)

含义:遍历数组中的每个元素(物品)。

for(intj=sum/2;j>=nums[i];j--)

含义:0-1 背包的关键:内层循环倒序遍历背包容量。这样可以保证每个元素只被使用一次(正序会导致完全背包问题)。

dp[j]=max(dp[j],dp[j-nums[i]]+nums[i]);

含义:状态转移方程。对于当前元素nums[i],要么不放入背包(保持dp[j]),要么放入背包(dp[j - nums[i]] + nums[i])。取较大值更新。

if(dp[sum/2]==sum/2)returntrue;

含义:遍历结束后,如果dp[sum/2]恰好等于sum/2,说明可以找到一个子集的和为sum/2,返回 true。

returnfalse;

含义:无法找到和为sum/2的子集,返回 false。


为什么内层循环要倒序?

以 nums = [1, 5] 为例,target = 3 正序(错误): i=0, num=1: dp[1] = max(dp[1], dp[0]+1) = 1 dp[2] = max(dp[2], dp[1]+1) = 2 <- 用到了本轮刚更新的 dp[1] dp[3] = max(dp[3], dp[2]+1) = 3 <- 用到了本轮刚更新的 dp[2] 结果:num=1 被使用了多次!变成完全背包了 倒序(正确): i=0, num=1: dp[3] = max(dp[3], dp[2]+1) dp[2] 还是旧值 dp[2] = max(dp[2], dp[1]+1) dp[1] 还是旧值 dp[1] = max(dp[1], dp[0]+1) = 1 结果:num=1 只被使用一次

复杂度分析

复杂度说明
时间复杂度O(n × sum/2)外层循环 n 次,内层循环 sum/2 次
空间复杂度O(sum/2)dp 数组大小为 sum/2 + 1

面试追问 FAQ

问题答案
为什么能转化成 0-1 背包问题?如果能找到和为 sum/2 的子集,剩下的元素和也是 sum/2,所以问题等价于「能否从数组中选若干元素使其和为 sum/2」
dp[j]的含义是什么?容量为 j 的背包最多能装多少重量的物品(即最多能达到多大的和)
为什么不直接用布尔数组dp[j]表示能否凑到 j?因为dp[j]表示「最多」能装多少,可以用来判断是否恰好装满;布尔数组需要额外记录具体方案
dp[sum/2] == sum/2为什么能判断「恰好装满」?dp[sum/2]是背包最多能装到的重量,如果它等于sum/2说明可以恰好装满
进阶:如何输出具体的分割方案?额外记录每个状态的选择路径,从dp[sum/2]开始回溯,找出被选中的元素
进阶:如何优化空间?使用 bitset:`bitset<10001> bits; bits[0] = 1; bits

相关题目

题号题目难度核心思路
416分割等和子集中等0-1 背包
322零钱兑换中等完全背包
279完全平方数中等动态规划
494目标和中等0-1 背包(计数)
1049最后一块石头的重量 II中等0-1 背包

总结

要点内容
核心思想将分割等和子集转化为 0-1 背包:能否从数组中选若干元素使其和为 sum/2
状态定义dp[j]= 容量为 j 的背包最多能装的重量(元素和)
状态转移dp[j] = max(dp[j], dp[j-nums[i]] + nums[i])
关键点内层循环倒序,保证每个元素只使用一次
边界条件sum 为奇数时直接返回 false
结果判断dp[sum/2] == sum/2表示恰好装满

http://www.rkmt.cn/news/1293555.html

相关文章:

  • Athas项目架构深度剖析:理解Tauri与React的完美结合
  • 在ComfyUI中轻松创造专业级AI视频:WanVideoWrapper完整指南
  • JavaScript项目部署与优化:让你的Awesome Projects更专业
  • ESPullToRefresh终极指南:10个技巧助你掌握iOS下拉刷新的企业级应用
  • React Native集成Llama.cpp:移动端本地大语言模型实践指南
  • UI-TARS-desktop深度解析:视觉语言模型驱动的智能桌面控制架构揭秘
  • p5.js Web Editor:零配置在线创意编程平台完全指南
  • 知识竞赛代表队分组方法详解
  • RISC-V开发踩坑实录:从编译错误‘csrr a5,mhartid’到GDB报错‘E14’的完整排错指南
  • 【独家首发】ElevenLabs未公开Turkish语音参数手册:/voice-settings中隐藏的5个关键调节项
  • Modbus Slave 模拟器配置实战:从零搭建工业通信测试环境
  • Docker一条命令部署kkFileView?这些隐藏的配置和优化技巧你可能不知道
  • 处理激光雷达(LiDAR)数据?手把手教你用PCL+PDAL转换LAS/LAZ和BIN点云到PCD格式
  • 电力测控实战:用Win10计算器搞定RCR低通滤波器的幅频与相移补偿
  • 终极指南:如何快速安装最新ADB驱动并解决Windows常见问题
  • TexLab性能优化终极指南:如何配置大型LaTeX项目获得最佳响应速度
  • GeoPattern颜色系统深度剖析:如何智能控制背景色与填充色
  • 欢迎使用Marp CLI
  • HTTPCanary Magisk模块终极指南:轻松突破Android HTTPS抓包限制的完整解决方案
  • 别再死记硬背PWM值了!用STM32 HAL库驱动MG90S舵机,一个公式搞定所有角度
  • 微服务配置治理实战:从硬编码到Nacos外部化配置迁移
  • 鼠标点击也能如此惊艳?这款开源工具让你每次点击都充满仪式感
  • 如何用BepInEx构建游戏插件系统:Unity与.NET游戏的终极模组框架指南
  • Programming Bitcoin最佳实践:10个核心编程技巧助你从零掌握比特币开发 [特殊字符]
  • Fluid安全架构详解:如何构建企业级数据安全防护体系
  • 高性能数据导入导出框架:企业级Excel处理解决方案架构设计
  • 测试平台接 AI,不是接个聊天框就完事了
  • 如何从Chrome浏览器中安全提取已保存的登录凭据
  • 深入浅出:三相并网逆变器SPWM控制中的向量图与相位关系详解(附PSIM仿真验证)
  • libde265安全编码实践:内存管理与错误处理的最佳方案