LeetCode 416. 分割等和子集
题目链接:416. 分割等和子集
思路:这道题乍一看可以用暴力解法做,但时间复杂度会很高,所以做这道题需要先进行一个等价转换,将题目要求转换为:是否可以从输入数组中挑出一些正整数,使得这些数的和等于整个数组元素的和的一半。(如果数组元素和为奇数,可以直接返回false。)并且本题的每一个元素只有不选择和选择一次两种方式,因此联想到使用01背包问题。
- 确定dp数组及其下标的定义
01背包问题可以使用二维数组,也可以使用一维数组,下面用两种语言来分别实现一维dp数组和二维dp数组的代码逻辑,因为dp数组的定义会直接影响到之后的递推公式等绝大部分逻辑。
如果定义dp数组为二维数组的话,那 d p [ i ] [ j ] dp[i][j] dp[i][j]就表示从数组的 [0, i] 这个区间内挑选一些正整数,每个数只能用一次,使得这些数的和最大。 d p [ i ] [ j ] dp[i][j] dp[i][j]的列可以设为背包容量(本题最大容量为整个数组元素和的一半), d p [ i ] [ j ] dp[i][j] dp[i][j]的行可以设为物品数量(本题为数组中各个索引位置的元素值)。
如果定义dp数组为一维数组的话,那 d p [ j ] dp[j] dp[j]就表示容量为j的背包,所背的物品价值(本题即为元素之和)最大为dp[j]。 - 确定递推公式
第i件物品(本题为第i个数)只有两种选择:选它或者不选它。- 选第 i 件物品
如果能选且选上第 i 件物品,则 d p [ i ] [ j ] = d p [ i − 1 ] [ j − n u m s [ i ] ] + n u m s [ i ] dp[i][j] = dp[i-1][j-nums[i]]+nums[i] dp[i][j]=dp[i−1][j−nums[i]]+nums[i] - 不选第 i 件物品
如果 j < nums[i],则 d p [ i ] [ j ] = d p [ i − 1 ] [ j ] dp[i][j] = dp[i-1][j] dp[i][j]=dp[i−1][j]
- 选第 i 件物品
- dp数组的初始化
因为dp数组的更新顺序是从i-1更新到i(从上至下),从j-1更新到j(从左至右),所以二维dp数组初始化的时候需要初始化左边界和上边界。左边界 d p [ i ] [ 0 ] dp[i][0] dp[i][0]的含义相当于背包容量为0时能达到的最大元素和,根据实际情况也为0,所以左边界全部初始化为0。上边界 d p [ 0 ] [ j ] dp[0][j] dp[0][j]的含义相当于背包容量为j时,能不能放入第一个元素nums[0],如果能的话就填入nums[0],如果不能的话则为0,也就是说,上边界从 d p [ 0 ] [ n u m s [ 0 ] ] dp[0][nums[0]] dp[0][nums[0]]及其之后的位置全部填入nums[0]。 - 确定遍历顺序
本题,先遍历物品再遍历背包,或者先遍历背包再遍历物品都可以,但按照一般的思维逻辑,先遍历物品,再每个物品里遍历背包的重量。 - 返回结果
返回 d p [ l e n ( n u m s ) − 1 ] [ t a r g e t ] = = t a r g e t dp[len(nums)-1][target]==target dp[len(nums)−1][target]==target 的结果。
Python版本(二维dp数组):
class Solution:
def canPartition(self, nums: List[int]) -> bool:
target = sum(nums)
if target % 2 == 1:
return False
else:
target //= 2
dp = [[0] * (target+1) for _ in range(len(nums))]
for i in range(nums[0], target+1):
dp[0][i] = nums[0]
for i in range(1,len(nums)):
for j in range(target+1):
if j < nums[i]: # 容量有限,无法选择第i个数字nums[i]
dp[i][j] = dp[i-1][j]
else:
dp[i][j] = max(dp[i-1][j], dp[i-1][j-nums[i]]+nums[i])
return dp[len(nums)-1][target]==target
时间复杂度: O ( n ∗ S u m / 2 ) O(n*Sum/2) O(n∗Sum/2),空间复杂度: O ( n ∗ S u m / 2 ) O(n*Sum/2) O(n∗Sum/2)
go版本(一维dp数组):
func canPartition(nums []int) bool {
sum := 0
for i:=0; i<len(nums); i++ {
sum += nums[i]
}
if (sum % 2 == 1) {
return false
}
target := sum/2
dp := make([]int, target+1)
for i:=0; i<len(nums); i++ {
for j:=target; j>=nums[i]; j-- {
if dp[j] < dp[j - nums[i]] + nums[i] {
dp[j] = dp[j - nums[i]] + nums[i]
}
}
}
return dp[target]==target
}
时间复杂度: O ( n ∗ S u m / 2 ) O(n*Sum/2) O(n∗Sum/2),空间复杂度: O ( S u m / 2 ) O(Sum/2) O(Sum/2)