leetcode题:494. 目标和(中等)

一、题目描述:494. 目标和(中等)

给定一个非负整数数组,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。
注意:

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

来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/target-sum
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。

二、解题思路

方法一、深度优先遍历

1、每次递归分每次两种情况,1)sum 加上nums[i],和2)sum不加上nums[i],然后进入下一层递归,将两次递归的返回结果累加返回。

2、递归的边界条件是,直到i==nums.size()并且sum == S,则返回1.。否则返回0。

优化,通过一个字典dp记录已经递归过的点(i,sum)如果map里存在节点(i,sum)则返回dp里的结果,不再递归,否则进行递归,然后再返回前将结果记录在dp里面。相当于使用一个map记录已经遍历过的子递归,做了剪枝处理。

方法二、动态规划

大神的方法

原问题是给定一些数字,加加减减,使得它们等于targert。例如,1 - 2 + 3 - 4 + 5 = target(3)。如果我们把加的和减的结合在一起,可以写成

(1+3+5)  +  (-2-4) = target(3)
-------     ------
 -> 正数    -> 负数
所以,我们可以将原问题转化为: 找到nums一个正子集和一个负子集,使得总和等于target,统计这种可能性的总数。

我们假设P是正子集,N是负子集。让我们看看如何将其转换为子集求和问题:

                  sum(P) - sum(N) = target
                  (两边同时加上sum(P)+sum(N))
sum(P) + sum(N) + sum(P) - sum(N) = target + sum(P) + sum(N)
            (因为 sum(P) + sum(N) = sum(nums))
                       2 * sum(P) = target + sum(nums)
因此,原来的问题已转化为一个求子集的和问题: 找到nums的一个子集 P,使得

sum(P) = \frac{target + sum(nums)}{2}
sum(P)= 
2
target+sum(nums)
​    
 

根据公式,若target + sum(nums)不是偶数,就不存在答案,即返回0个可能解。

因此题目转化为01背包,也就是能组合成容量为sum(P)的方式有多少种,一种组合中每个数字只能取一次。解决01背包问题使用的是动态规划的思想。

方法是

开辟一个长度为P+1的数组,命名为dp
dp的第x项,代表组合成数字x有多少方法。比如说,dp[0] = 1,代表组合成0只有1中方法,即什么也不取。比如说dp[5] = 3 ,代表使总和加到5总共有三种方法。
所以最后返回的就是dp[P],代表组合成P的方法有多少种
问题是

怎么更新dp数组呢?

遍历nums,遍历的数记作num
再逆序遍历从P到num,遍历的数记作j
更新dp[j] = dp[j - num] + dp[j]
这样遍历的含义是,对每一个在nums数组中的数num而言,dp在从num到P的这些区间里,都可以加上一个num,来到达想要达成的P。
举例来说,对于数组[1,2,3,4,5],想要康康几种方法能组合成4,那么设置dp[0]到dp[4]的数组
假如选择了数字2,那么dp[2:5](也就是2到4)都可以通过加上数字2有所改变,而dp[0:2](也就是0到1)加上这个2很明显就超了,就不管它。
以前没有考虑过数字2,考虑了会怎么样呢?就要更新dp[2:5],比如说当我们在更新dp[3]的时候,就相当于dp[3] = dp[3] + dp[1],即本来有多少种方法,加上去掉了2以后有多少种方法。因为以前没有考虑过2,现在知道,只要整到了1,就一定可以整到3。
为什么以这个顺序来遍历呢?
假如给定nums = [num1,num2,num3],我们现在可以理解dp[j] = dp[j-num1] + dp[j-num2] + dp[j-num3]。

但是如何避免不会重复计算或者少算?要知道,我们的nums并不是排序的,我们的遍历也不是从小到大的。

我们不妨跟着流程走一遍

第一次num1,仅仅更新了dp[num1] = 1,其他都是0+0都是0啊都是0
第二次num2,更新了dp[num2] = 1和dp[num1+num2] = dp[num1+num2] + dp[num1] = 1,先更新后者。
第三次num3,更新了dp[num3] = 1和dp[num1+num3] += 1和dp[num2+num3] += 1和dp[num1+num2+num3] += 1。按下标从大到小顺序来更新。
......
由此可见,这种顺序遍历能得到最后的答案。这里可以跟着IDE的debug功能走一遍,加深理解

 

三、代码

方法一

 int findTargetSumWay_dfs(vector<int>& nums, int S) {
        if(nums.size() == 0)
            return 0;
        int index = 0;
        map<pair<int,int>,int> save;
        int add_nums = findTargetSumWays(nums, index + 1,S,0+nums[index],save);
        int mul_nums = findTargetSumWays(nums, index + 1,S,0-nums[index],save);
        return add_nums + mul_nums;
    }
    int findTargetSumWays(vector<int> & nums, int index , int &s,int sum,map<pair<int,int>,int> & save)
    {
        pair<int,int> p(index,sum);
        if(save.count(p) > 0)
            return save[p];
        if(index == nums.size() && sum == s)
            return 1;
        if(index == nums.size())
            return 0;
        int add_nums = 0;
        if(INT_MAX - nums[index] > sum)
            add_nums = findTargetSumWays(nums, index + 1,s,sum+nums[index],save);
        
        int mul_nums = 0;
        if(INT_MIN + nums[index] < sum)
            mul_nums = findTargetSumWays(nums, index + 1,s,sum-nums[index],save);
        int ret = add_nums + mul_nums;
        save[p] = ret;
        return ret;
    }

方法二、

    int findTargetSumWays(vector<int>& nums, int S) {
        if(nums.size() == 0)
            return 0;
        int sums = 0;
        if(nums.size()==1)
        {
            if(nums[0] == S || -1*nums[0] == S)
                return 1;
            return 0;
        }
        for(int i = 0; i < nums.size(); i++)
        {
            sums += nums[i];
        }
        if(sums < S || -1*sums > S)
            return 0;
        if((sums+S)%2 ==1)
            return 0;
        int r = (sums - S)/2 + S;
        vector<vector<int>> dp(nums.size(),vector<int>(r+1,0));
        cout<<"r="<<r<<endl;
        if(nums[0] <= r)
        {
            dp[0][nums[0]] = 1;
        }
        for(int i = 1; i < nums.size(); i ++)
        {
            for(int j = 0; j <= r; j++)
            {
                
                if(nums[i] == j)
                {
                    dp[i][j] = 1;
                }
                if(j - nums[i] >= 0)
                {
                    dp[i][j] = dp[i-1][j] + dp[i-1][j-nums[i]] + dp[i][j];
                }
                else
                {
                    dp[i][j] = dp[i-1][j] + dp[i][j];
                }
                cout<<dp[i][j]<<",";
                
            }
            cout<<endl;
        }
        if(sums == 0&& S == 0 || S == -1*sums)
            return dp[nums.size()-1][r] + 1 ;

        return dp[nums.size()-1][r];
    }

方法二 Python

class Solution:
    def findTargetSumWays(self, nums: List[int], S: int) -> int:
        length, dp = len(nums), {(0,0): 1}
        for i in range(1, length+1):
            for j in range(-sum(nums), sum(nums) + 1):
                dp[(i,j)] = dp.get((i - 1, j - nums[i-1]), 0) + dp.get((i - 1, j + nums[i-1]), 0)             
        return dp.get((length, S), 0)

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
经导师精心指导并认可、获 98 分的毕业设计项目!【项目资源】:微信小程序。【项目说明】:聚焦计算机相关专业毕设及实战操练,可作课程设计与期末大作业,含全部源码,能直用于毕设,经严格调试,运行有保障!【项目服务】:有任何使用上的问,欢迎随时与博主沟通,博主会及时解答。 经导师精心指导并认可、获 98 分的毕业设计项目!【项目资源】:微信小程序。【项目说明】:聚焦计算机相关专业毕设及实战操练,可作课程设计与期末大作业,含全部源码,能直用于毕设,经严格调试,运行有保障!【项目服务】:有任何使用上的问,欢迎随时与博主沟通,博主会及时解答。 经导师精心指导并认可、获 98 分的毕业设计项目!【项目资源】:微信小程序。【项目说明】:聚焦计算机相关专业毕设及实战操练,可作课程设计与期末大作业,含全部源码,能直用于毕设,经严格调试,运行有保障!【项目服务】:有任何使用上的问,欢迎随时与博主沟通,博主会及时解答。 经导师精心指导并认可、获 98 分的毕业设计项目!【项目资源】:微信小程序。【项目说明】:聚焦计算机相关专业毕设及实战操练,可作课程设计与期末大作业,含全部源码,能直用于毕设,经严格调试,运行有保障!【项目服务】:有任何使用上的问,欢迎随时与博主沟通,博主会及时解答。
经导师精心指导并认可、获 98 分的毕业设计项目!【项目资源】:微信小程序。【项目说明】:聚焦计算机相关专业毕设及实战操练,可作课程设计与期末大作业,含全部源码,能直用于毕设,经严格调试,运行有保障!【项目服务】:有任何使用上的问,欢迎随时与博主沟通,博主会及时解答。 经导师精心指导并认可、获 98 分的毕业设计项目!【项目资源】:微信小程序。【项目说明】:聚焦计算机相关专业毕设及实战操练,可作课程设计与期末大作业,含全部源码,能直用于毕设,经严格调试,运行有保障!【项目服务】:有任何使用上的问,欢迎随时与博主沟通,博主会及时解答。 经导师精心指导并认可、获 98 分的毕业设计项目!【项目资源】:微信小程序。【项目说明】:聚焦计算机相关专业毕设及实战操练,可作课程设计与期末大作业,含全部源码,能直用于毕设,经严格调试,运行有保障!【项目服务】:有任何使用上的问,欢迎随时与博主沟通,博主会及时解答。 经导师精心指导并认可、获 98 分的毕业设计项目!【项目资源】:微信小程序。【项目说明】:聚焦计算机相关专业毕设及实战操练,可作课程设计与期末大作业,含全部源码,能直用于毕设,经严格调试,运行有保障!【项目服务】:有任何使用上的问,欢迎随时与博主沟通,博主会及时解答。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值