leetcode494.目标和

40 阅读2分钟
给定一个非负整数数组,a1, a2, ..., an, 和一个目标数,
S。现在你有两个符号 + 和 -。
对于数组中的任意一个整数,你都可以从 + 或 -中选择一个符号添加在前面。
返回可以使最终数组和为目标数 S 的所有添加符号的方法数。
示例:
● 输入:nums: [1, 1, 1, 1, 1], S: 3
● 输出:5
解释:
● -1+1+1+1+1 = 3
● +1-1+1+1+1 = 3
● +1+1-1+1+1 = 3
● +1+1+1-1+1 = 3
● +1+1+1+1-1 = 3
一共有5种方法让最终目标和为3

思路:转化成子集求和

子集X(所有+号的数组),子集N(所有带-号的数组) sum=X-N,N=sum-X。 我们要求的是X-N=target,

X-(sum-X)=target---->X=(target+sum)/2

把问题转化为,装满容量为X的背包,有几种方法。

为什么可以转换成01背包问题?

假设X是所有正整数的集合,最后推出X=(target+sum)/2,就是我们已经知道了X的值了,target和sum都是已知的,只要我们从nums里取值把X装满就好了,而且取的都是正数。

动态规划

- 确定dp数组以及下标的含义

- dp[j]表示用当前可用的数组能组成总和j的不同方法数
- 二维dp的话:dp[i][j]:使用下标为[0, i]的nums[i]能够凑满j(包括j)这么大容量的包,有dp[i][j]种方法。

- 举例推导dp数组

此时我们只有一个数字1可以用,所以dp[1]=1 image.png 此时有两个1可以用,dp[1]=2,dp[2]=1 image.png

image.png

image.png

image.png 我们可以得到递归公式

 dp[j] =dp[j]+dp[j - nums[i]]

循环的顺序

外循环应该是nums[i],内循环是target。

用二维数组会看起来更直观一点,我们作为初学者,用二维数组dp可以更清晰看见整个过程。

image.png

image.png

image.png

image.png

class Solution {
    public int findTargetSumWays(int[] nums, int target) {
        int sum=0;
        for(int i=0;i<nums.length;i++){
            sum=sum+nums[i];
        }
        // 如果 (target + sum) 是奇数或者 sum < target,则不可能分割成两个子集
        if ((sum + target) % 2 != 0 || sum < target) {
            return 0;
        }
        int bag=(target+sum)/2;
        if(bag<0){
            return 0;
        }
        int[][] dp=new int[nums.length+1][bag+1];
         //dp数组的含义:从下标为i的nums中取元素,放到容量为j的背包中   
        for(int i=0;i<=nums.length;i++){
           dp[i][0]=1;
        }
        for(int i=1;i<=nums.length;i++){
            for(int j=0;j<=bag;j++){
                if(j<nums[i-1]){
                     dp[i][j] = dp[i - 1][j];
                }else{
                     dp[i][j]=dp[i-1][j]+dp[i-1][j-nums[i-1]];
                }
               
            }
        }
        return dp[nums.length][bag];
    }
}