
代码1:
- 长度小于2,特判false,和为奇数,特判false,最大值大于和的一半,特判false
- 接下来,0-1背包问题,动态规划,建立
dp[i][j]bool二维切片,i 表示nums[:i+1],j 表示和,也就是0-1背包里的重量,dp[i][j]bool就表示在nums[:i+1]中是否可以找出和为 j 的情况
- 初始化dp切片,第一列,也就是 j 等于 0 的情况,那自然是true,因为你只需要一个数字不选,那么和就是0,
- 初始化第一排,也就是
i == 0 的情况,i == 0,除了刚好 j == nums[i]这种情况,其余都是false,因为初始化默认就是false,所以我们只需要dp[0][nums[0]] = true就可以了(但其实不初始化这个也可以)
i > 0, j > 0的情况,我们只需要考虑当前的nums[i]这个值我们选不选就可以了,这两种情况只要一个满足true,当前的dp就可以为true,即dp[i][j] = dp[i-1][j] || dp[i-1][j-v],
- 但是我们要保证 j-v > 0,也就是当前值一定要小于j,如果大于,我们只能选择,不选这个值这种情况
dp[i][j] = dp[i-1][j]
func canPartition(nums []int) bool {
n := len(nums)
if n < 2 {
return false
}
a, max := 0, 0
for _,v := range nums {
a = a + v
if max < v {
max = v
}
}
if a % 2 != 0 {
return false
}
a = a >>1
if a < max {
return false
}
dp := make([][]bool, n)
for i := range dp {
dp[i] = make([]bool,a+1)
}
for i := 0; i < n; i++ {
dp[i][0] = true
}
dp[0][nums[0]] = true
for i := 1; i < n; i++ {
v := nums[i]
for j := 0; j <= a; j++ {
if j >= v {
dp[i][j] = dp[i-1][j] || dp[i-1][j-v]
}else {
dp[i][j] = dp[i-1][j]
}
}
}
return dp[n-1][a]
}
代码2:
- 但是可以发现在计算 dp 的过程中,每一行的 dp 值都只与上一行的 dp 值有关,因此只需要一个一维数组即可此时的转移方程为:
dp[j]=dp[j] ∣dp[j−nums[i]]
- 注意第二层循环需要从大到小计算,因为如果我们不使用辅助空间,直接在原dp切片上直接进行操作,那么在计算
dp[j]=dp[j−nums[i]]时,dp[j−nums[i]]已经是更新过的状态,不再是上一行的dp值了,
- 既然是从大到小计算,j < v 的情况也不需要考虑了,直接继承就可以了,不用更新
func canPartition(nums []int) bool {
n := len(nums)
if n < 2 {
return false
}
a, max := 0, 0
for _,v := range nums {
a = a + v
if max < v {
max = v
}
}
if a % 2 != 0 {
return false
}
a = a >>1
if a < max {
return false
}
dp := make([]bool, a+1)
dp[0] = true
for i := 1; i < n; i++ {
v := nums[i]
for j := a; j >= v; j-- {
dp[j] = dp[j] || dp[j-v]
}
}
return dp[a]
}