11.目标和

给定一个非负整数数组,a1, a2, ..., an, 和一个目标数,S。现在你有两个符号 + 和 -。对于数组中的任意一个整数,你都可以从 + 或 -中选择一个符号添加在前面。

返回可以使最终数组和为目标数 S 的所有添加符号的方法数。

示例 1:

输入: 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。

注意:

  1. 数组的长度不会超过20,并且数组中的值全为正数。
  2. 初始的数组的和不会超过1000。
  3. 保证返回的最终结果为32位整数。

 

几个星期前做这题的时候,我确信只能用搜索来解决。

因为这道题它要把数组所有元素都用上,

因为这题我当时确实想不到怎么拆分成一个个小问题

dp[n]代表目标和为n的方案个数?

算出【1,1,1,1,1】目标和为n-1的方案个数,和算出目标和为n的方案个数有什么关系吗?

 

后来在动态规划又看见它,出于好奇我看了看别人是怎么做的

他们没有直接开始做,而是把问题转化一下,使得动态规划成为可能

 

1、转化为dp问题:

设数组为s(num),记和为num。

记目标和为tar

由题目我们可以知道,有的元素前加正号,有的加负号,这些元素加上符号后总和为目标值tar

把所有加正号的元素归入数组s(正),其和为p

把所有加负号的元素归入数组s(负),注意我们还没加负号,s(负)里面的元素还是正的。其和为p

我们可以知道:

①s(正)+s(负)=s(num),所以 p+q=num

②s(正)-s(负)=tar,所以 p-q=tar

③综合:2p=tar+num(p为整数,所以tar+num必须为偶数!)

tar和num已知,只要求出s(num)里面有多少个子集的和为p,就有多少种方案

 

绕来绕去,好像又绕回求目标和了,好像只是从求tar变成了求p而已,好像还是没有什么变化啊。

但是!求tar要把数组元素都用个遍,求p只要从数组里选几个元素就行了,不一定都用一遍

 

 

2、动态规划求解

题目转化为:给定一个正整数数组 nums。从中选取若干元素(不重复),使得元素和为p。求选取元素的方法和

 

思路:

①总思路

dp[n]代表凑出n的方案数量。

给nums[0],看看能凑出什么数,再给nums[1]看看。。。

最后把nums数组里面的元素都给出去完,题目也就解出来了。

 

②如何表示凑数字?

设c(m)是已知凑出数字m的方法,n是nums里面的元素

那么,c(m)=c(m-n)+n

注意,如果c(m)=c(m-n)+c(n)可能会导致重复利用元素:

比如数组[1,2,5]

c(3)=1+2,c(2)=2

如果说c(5)=c(3)+c(2),那么很明显,数组里面的元素2被用了两次

 

③如何知道能凑出什么数字?

假设dp[N],nums元素n,

由②我们知道c(m)=c(m-n)+n,且c(m-n)方案个数已知

那么很明显,在加上n的情况下,m有dp[m-n]种凑法,即dp[m]+=dp[m-n]

 

现在只探索了怎么凑出m,但是实际上一定就能凑出m?一定只有m被凑出来?

数字 n~N ,都有可能被凑出来。我们需要遍历dp[n]~dp[N]

 

④如何遍历?

接上③

不就是从dp[n]遍历到dp[N]嘛,有什么可说的呢?

我当时也是这么想的,结果出了大错,计算重复了!

为什么?从因为c(N)很可能由c(n)凑出的!

举个栗子:

数组[1,2,3,4,5]

s(5)=s(1)+4,s(9)=s(5)+4

发现了什么?元素4被用了两次!

即s(9)=s(5)+4这种情况,包含4+4+s(1)这种,且这种情况是不对的,因为4被用了两次

 

说到这里,算法的基本思路都说完了

 

 

3、例题

数组nums[1,2,3,4,5],从里面不重复,随机挑选若干元素,使得和为9

(这题我在https://blog.youkuaiyun.com/mine_song/article/details/70216562?utm_source=copy上看的,选择自己分析一遍)

 

定义dp[10](即0~9),dp[n]为凑出n的方案个数,c(n)为已知凑出 n 的方案

dp[n]初始为0

①dp[0]:0无论如何都凑得出,记为1;

②1:当只有1时,

dp[9-1] ,dp[8-1] 。。。。 dp[1-1] 都是0,不写了

dp[1]+=dp[1-1],dp[1]=1;

dp=[1,1,0,0,0,0,0,0,0,0]

 

③1,2:元素1已经在上面用过了,所以只看元素2:

同上,由于dp[9-2] , dp[8-2] 。。。 dp[4-2] 都是0,所以我不写

dp[3]+=dp[3-2],dp[3]=1;

dp[2]+=dp[2-1],dp[2]=1;

dp=[1,1,1,1,0,0,0,0,0,0]

 

③1,2,3:元素1,2已经用过,看元素3:

dp[9-3] , dp[8-3] , dp[7-3] 都是0,不写

dp[5]+=dp[5-3],dp[5]=1;

dp[4]+=dp[4-3],dp[4]=1;

dp[3]+=dp[3-3],dp[3]=2;

dp=[1,1,2,1,1,1,0,0,0,0]

 

 

据此一路分析下去就把答案解出来了

 

 

代码:

class Solution {
    public int findTargetSumWays(int[] nums, int s) {
        int sum = 0;
        for(int i=0;i<nums.length;i++)
            sum+=nums[i];
        if(s>sum||(sum+s)%2!=0)
            return 0;
        int t=(sum+s)/2;
        int[] dp=new int[t+1];
        dp[0]=1;
        for(int i=0;i<nums.length;i++){
            for(int j=t;j>=nums[i];j--){
                dp[j]+=dp[j-nums[i]];
            }
        }
        return dp[t];
    }
}

 

 

 

 

 

 

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值