【Leetcode】416.分割等和子集
416.分割等和子集
题目
给定一个只包含正整数的非空数组。是否可以将这个数组分割成两个子集,使得两个子集的元素和相等。
注意:
每个数组中的元素不会超过 100,数组的大小不会超过 200
示例 1:
输入: [1, 5, 11, 5]
输出: true
解释: 数组可以分割成 [1, 5, 5] 和 [11].
示例 2:
输入: [1, 2, 3, 5]
输出: false
解释: 数组不能分割成两个元素和相等的子集.
方法(动态规划)
题目的意思我们可以换一种方式理解:假设数组nums的元素和为sum,是否能找到数组的一个子序列,使得它的和为sum/2。
在起初,我们可以先进行以下判断:
- 如果nums的元素和sum为奇数,则不可能找到和为sum/2的子序列(因为nums中元素都为整数,和不可能为一个小数),直接返回false。
- 如果nums中最大的元素大于sum/2,也就意味着nums中其余所有元素的和小于sum/2,直接返回false。
1.状态定义
定义布尔类型的二维数组dp,dp[i][j]表示数组nums[0…i]中是否有和为j的子序列。
2.Base Case
base case为这个二维数组的第一行和第一列。
- j=0时,无论i为多少,都为true。因为任何一个nums[0…i]都有一个和为0的子序列(子序列中元素为0)。所以,dp[i][0] = true
- i = 0时,只能选择数组的第一个元素nums[0],因此当j与nums[0]相等时为true,其余都为false。即,dp[0][nums[0]] = true
3.状态转换方程
当遍历到一个数nums[i],我们有两种选择:选它或者不选它来构建和为j的子序列,这两种选择只要有一个为true,dp[i][j]就为true。
我们分为两种情况来讨论:
- j >= nums[i]时:
- 如果选nums[i],那么只须判断nums[0…i-1]是否有和为j-nums[i]的子序列即可。即dp[i][j] = dp[i - 1][j - nums[i]]
- 如果不选nums[i],则dp[i][j] = dp[i - 1][j]
- j < nums[i]时:
这种情况下不可以选nums[i],因为一旦选nums[i]进子序列,由于nums[i] > j,子序列的和一定大于j,不可能等于j。因此dp[i][j]一定为false。因此dp[i][j] = dp[i - 1][j]
最后返回dp[n-1][target]即可,即nums数组中是否有和为target的子序列(target为sum/2)
代码
1 |
|
优化空间复杂度
在填充dp数组时我们发现,每一行的dp值都只与它上一行的dp值有关,因此我们可以优化掉行这个维度,只留列的维度。于是可以将dp数组由二维将为一维。
空间复杂度由O(n*target)降到O(target)
1 |
|