来自专栏《LeetCode高频面试题》 欢迎订阅

目录

  • 1、题目
  • 2、思路
  • 3、二维c++代码
  • 4、二维java代码
  • 5、一维优化
  • 6、一维c++代码
  • 7、一维java代码

1、题目

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

示例 1:

输入:nums = [1,5,11,5]
输出:true
解释:数组可以分割成 [1, 5, 5] 和 [11] 。

示例 2:

输入:nums = [1,2,3,5]
输出:false
解释:数组不能分割成两个元素和相等的子集。

提示:

  • 1 <= nums.length <= 200
  • 1 <= nums[i] <= 100

2、思路

(动态规划) O(n∗m)O(n * m)O(n∗m)

给定一个只包含正整数的非空数组 nums[0],判断是否可以从数组中选出一些数字,使得这些数字的和恰好等于整个数组的元素和的一半。

样例:

如样例所示,nums = [1,5,11,5],数组可以分割成 [1, 5, 5][11],因此返回ture

从题意来看,这个问题可以转换成0-1背包问题,如何看出来的? 我们不妨将换种表述方式:

将大小为n的数组看成n件物品,数组元素和sum的一半看成一个容量为sum / 2的背包, 每件物品只能使用一次,每件物品的体积是nums[i],求解是否可以选出一些物品,使得这些物品的总体积恰好为背包的容量,因此可以使用动态规划求解,下面我们来讲解具体做法。

首先,如果sum 是奇数,则不可能将数组分割成元素和相等的两个子集,因此直接返回false。接下来我们去定义状态表示和推导状态转移方程。

状态表示: f[i][j]表示从前i个数中选若干个数,是否使得这些数字的和恰好等于j。因此f[i][j]有两种状态,true或者false

状态计算:

假定nums[]数组下标从1开始,如何确定f[i][j]的值?

一般去考虑最后一步,那么对于当前的数字 nums[i],可以选取也可以不选取:

  • 1、不选 nums[i],那么我们就从前i - 1个数中选,看是否使得这些数字的和恰好等于j,即 f[i][j] = f[i - 1][j]
  • 2、选择nums[i] ,在背包可以装下的情况下,那么相应的背包容量就要减去nums[i] ,f[i][j]的状态就可以从f[i - 1][j - nums[i]]转移过来,即f[i][j] = f[i - 1][j - nums[i]]

综上,两种情况只要有一个为truef[i][j]就为true。因此状态转移方程为f[i][j] = f[i - 1][j] | f[i - 1][j - nums[i]]

初始化:

f[0][0] = true:在前0个数中,我们可以一个数都不去选,因此从前0个数中选,使得这些数字的和恰好等于0的状态为true,其余的状态都初始化为false

实现细节:

在推导状态转移方程时,我们假设的nums[]数组下标是从1开始的,而实际中的nums[]数组下标是从0开始的,因此在代码的编写过程中,我们需要将所有nums[i]的下标减去 1,与使用的语言保持一致。

时间复杂度分析: O(n∗m)O(n * m)O(n∗m),nnums数组的大小,m数组元素和的一半。

空间复杂度分析:O(n∗m)O(n * m)O(n∗m)

3、二维c++代码

class Solution {public:bool canPartition(vector<int>& nums) {int n = nums.size(), sum = 0;for(int x : nums) sum += x;if(sum % 2) return false;int m = sum / 2;vector<vector<bool>> f(n + 1, vector<bool>(m + 1, false));f[0][0] = true;for(int i = 1; i <= n; i++){for(int j = 1; j <= m; j++){if(j >= nums[i - 1]) f[i][j] = f[i - 1][j - nums[i - 1]] | f[i - 1][j];else f[i][j] = f[i - 1][j];}}return f[n][m];}
};

4、二维java代码

class Solution {public boolean canPartition(int[] nums) {int n = nums.length, sum = 0;for(int x : nums) sum += x;if(sum % 2 != 0) return false;int m = sum / 2;boolean[][] f = new boolean[n + 1][m + 1];f[0][0] = true;for(int i = 1; i <= n; i++){for(int j = 1; j <= m; j++){if(j >= nums[i - 1]) f[i][j] = f[i - 1][j - nums[i - 1]] || f[i - 1][j];else f[i][j] = f[i - 1][j];}}return f[n][m];}
}

5、一维优化

我们可以发现,在计算 f[i][j]的过程中,每一行f[i][j]的值只与上一行的f[i - 1][j]有关,因此考虑去掉前一维,则状态转移方程为:f[j] = f[j] | f[j - nums[i]]

如果此时我们继续考虑第二层循环j从小往大计算,即:

for (int i = 1; i <= n; i++){                  //为了下标对应,实际nums[i]应取nums[i - 1]for (int j = nums[i]; j <= m; j++){f[j] = f[i] | f[j - nums[i]];   }
}

此时的状态便与二维的状态不等价了,因为在计算第i层的状态时,我们从小到大枚举, j - nums[i]严格小于j,那么f[ j- nums[i]]一定会先于f[j]被计算出来,于是我们计算出来的f[j - nums[i]]仍为第i层状态,这样f[j - nums[i]]等价于f[i][j-nums[i]] ,实际上f[j - nums[i]]应该等价于f[i - 1][j - nums[i]]

为了解决这个问题只需要将j从大到小枚举。

for (int i = 1; i <= n; i++){                  //为了下标对应,实际nums[i]应取nums[i - 1]for (int j = m; j >= nums[i]; j -- ){f[j] = f[i] | f[j - nums[i]];   }
}

因为我们从大到小枚举j,而 j - nums[i]严格小于j,于是我们在计算f[j] 的时候f[j - nums[i]]还未被第i层状态更新过,那么它存的就是上一层(i - 1层)的状态,即等价于f[i - 1][j - nums[i]]

空间复杂度分析:O(n)O(n)O(n)

6、一维c++代码

class Solution {public:bool canPartition(vector<int>& nums) {int n = nums.size(), m = 0;for (int x: nums) m += x;if (m % 2) return false;m /= 2;vector<bool> f(m + 1);f[0] = true;for (int i = 1; i <= n; i++)for (int j = m; j >= nums[i - 1]; j -- )f[j] = f[j] | f[j - nums[i - 1]];return f[m]; }
};

7、一维java代码

class Solution {public boolean canPartition(int[] nums) {int n = nums.length, m = 0;for (int x: nums) m += x;if (m % 2 != 0) return false;m /= 2;boolean[] f = new boolean[m + 1];f[0] = true;for (int i = 1; i <= n; i++)for (int j = m; j >= nums[i - 1]; j -- )f[j] |= f[j - nums[i - 1]];return f[m];}
}
如果我的文章对你有帮助的话,欢迎一键三连!!!

原题链接: 416. 分割等和子集

LeetCode 416. 分割等和子集 【c++/java详细题解】相关推荐

  1. LeetCode 416 分割等和子集

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

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

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

  3. leetcode - 416. 分割等和子集

    416. 分割等和子集 -------------------------------------------- 给定一个只包含正整数的非空数组.是否可以将这个数组分割成两个子集,使得两个子集的元素和 ...

  4. [动态规划] leetcode 416. 分割等和子集

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

  5. leetcode 416. 分割等和子集

    分割等和子集题解集合 DFS 记忆化搜索 记忆化搜索的另一种写法 动态规划 「滚动数组」解法 「一维空间优化」解法 DFS 思路 题意就是:给你一个非空数组,和为sum,你能否找到一个子序列,和为su ...

  6. leetcode 416. Partition Equal Subset Sum | 416. 分割等和子集(Java)

    题目 https://leetcode.com/problems/partition-equal-subset-sum/ 题解 乍一看是背包问题,但不同的是,本题要求结果等于某值,背包要求结果小于某值 ...

  7. 【LeetCode笔记】416. 分割等和子集(Java、动态规划、背包问题、滚动数组)

    文章目录 题目描述 思路 && 代码 1. 动态规划 O(nc) .O(nc) 2. 结合滚动数组 O(nc).O(c) 二刷 打卡第十四天-熬夜也得把题目补上= = 题目描述 初看题 ...

  8. LeetCode 416. 分割等和子集(动态规划)

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

  9. LeetCode 416. 分割等和子集(动态规划)(0-1背包)

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

最新文章

  1. 车联网空间巨大 解决网络安全问题乃当务之急
  2. ERA5、ERA-interm是啥
  3. python交互式绘图比较_python – 基于Tkinter和matplotlib的交互式绘图
  4. 子类重载父类函数_Python面向对象之继承、重写与重载
  5. 一次澄清:数据分析思维五大误区
  6. 天气预报城市代码对应表
  7. aso优化师是什么_什么是ASO优化?
  8. win10查看计算机管理员,肿么查看win10微软账户是否为管理员账户
  9. 华为初面+综合面试(Java技术面)附上面试题
  10. 布同:3D网游开发整体流程简述
  11. 区块链技术的风险!(转载)
  12. pytorch--初识张量与常见基础函数(1)
  13. Kotlin高仿微信-第14篇-单聊-视频通话
  14. 大学生计算机系统推荐,上大学买电脑?最适合大学生学习用的Windows笔记本电脑推荐!...
  15. 展望下未来的计算机400字,展望未来作文400字
  16. Parasol and GreenSwitch: Managing Datacenters Powered by Renewable Energy
  17. 简单win32程序创建控件
  18. 关于python中如何导入pygame模块(超详细)
  19. 整理了 34 个被吹爆了的Python开源框架
  20. Windows Phone灵魂诠释:Metro UI界面完全解析

热门文章

  1. 什么是阿里云备案服务号及相关疑问解答
  2. KERNEL32相关函数
  3. 三阶实对称矩阵含重根的快速还原秒杀法
  4. 基于可穿戴传感器和深度卷积神经网络运动状态进行下半身监控算法
  5. 2021Java高级面试题总结:java程序员优秀简历模板
  6. 如何设置windows防火墙的出入规则
  7. 数据挖掘技术特写(转)
  8. WPS压力传感器测高温部件的注意事项
  9. 生育应用程序Flo与FTC协议解决滥用健康数据问题
  10. c语言中减号算一个字符吗,C语言中指针的加减运算