第四周:( LeetCode494 ) Target Sum(c++)

来源:互联网 发布:onvifdiscovery java 编辑:程序博客网 时间:2024/06/06 18:28

原题:You are given a list of non-negative integers, a1, a2, …, an, and a target, S. Now you have 2 symbols + and -. For each integer, you should choose one from + and - as its new symbol.
Find out how many ways to assign symbols to make sum of integers equal to target S.

Example 1:
Input: nums is [1, 1, 1, 1, 1], S is 3.
Output: 5
Explanation:
-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
There are 5 ways to assign symbols to make the sum of nums be target 3.

Note:
The length of the given array is positive and will not exceed 20.
The sum of elements in the given array will not exceed 1000.
Your output answer is guaranteed to be fitted in a 32-bit integer.

思路:题目的大致意思是给定一组非负数,在数的前面任意加上“+”或者“-”,求解最后结果等于s共有多少种可能。题目中提示,数组的最大长度为20,算出来的可能的和结果不大于1000,输出结果在int范围内。
本题笔者试图用暴力法、深度优先搜索、二叉树剪枝和动态规划四种方法求解,并试图对各自优劣进行简要分析。

解法一:暴力法(超时)
思路:刚开始没有仔细看题目的提示,想着水一水过。又想着用dfs没有剪枝的话其实复杂度也是o(2^20),水一水也是o(2^20),就先水一水。结果跑了一半样例果然超时。究其原因可能是vector的操作用的时间比较多。
代码:

#include <cmath>#include <algorithm>class Solution {public:    int findTargetSumWays(vector<int>& nums, int S) {        vector<int> pre_result,now_result;        //pre_result为上次计算的结果,now_result为本次计算的结果        int now_num;        //now_num为本次循环要操作的数        pre_result.push_back(0);        for(int i=0;i<nums.size();i++){            now_num=nums[i];            now_result.clear();            for(int j=0;j<pow(2,i+1);j++){                if(j%2==0)                    now_result.push_back(pre_result[j/2]+nums[i]);                else                    now_result.push_back(pre_result[j/2]-nums[i]);            }            pre_result.clear();            pre_result.assign(now_result.begin(),now_result.end());        }        return count(now_result.begin(),now_result.end(),S);    }};

解法二:剪枝法(93ms通过)
思路:思路一没有过之后,想着其实每层计算之后应该会有结果相同的值出现,这时可以合并这些相同的值,进行适当的剪枝。如果每一层都进行剪枝,那么到了第20层节省下来的时间应该相当可观!而且根据题目的条件,最后结果和不超过1000,和最大的情况就是都取“+”的时候,都取“+”结果<=1000,那么都取“-”结果>=-1000。说明每一层至少可以压缩在2*1000+1(-1000到1000)个数以内,那么20层就是o(20*2001)。
代码:

#include <algorithm>class Solution {public:    static bool comp(const pair<int,int> &a,const pair<int,int> &b){        return a.second<b.second;    }    int findTargetSumWays(vector<int>& nums, int S) {        vector<pair<int,int>> pre_result,now_result;        //pre_result的第二个数值保存计算结果,第一个数值保存该计算结果出现的次数        int now_num;        pre_result.push_back(make_pair(1,0));        for(int i=0;i<nums.size();i++){            now_num=nums[i];            now_result.clear();            for(int j=0;j<(2*pre_result.size());j++)                if(j%2==0)                    now_result.push_back(make_pair(pre_result[j/2].first,pre_result[j/2].second+nums[i]));                else                    now_result.push_back(make_pair(pre_result[j/2].first,pre_result[j/2].second-nums[i]));            sort(now_result.begin(),now_result.end(),comp);            //对每一层循环得到的结果依据sum的大小进行排序,排序是为了下面对相同的sum进行合并剪枝            pre_result.clear();            pre_result.push_back(make_pair(now_result[0].first,now_result[0].second));            for(int j=1;j<now_result.size();j++)                if(now_result[j].second!=now_result[j-1].second)                    pre_result.push_back(make_pair(now_result[j].first,now_result[j].second));                else                    pre_result[pre_result.size()-1].first+=now_result[j].first;        }        for(int i=0;i<pre_result.size();i++){            if(pre_result[i].second==S)                return pre_result[i].first;        }        return 0;    }};

解法三:动态规划(19ms通过)
思路:解法二已经分析,每一层的和的个数不会超过2001个,所以用一个长度为2001的数组足以保存每一层的结果。其中数组下标表示和,数组的每一个值表示该结果出现的次数。复杂度也是o(20*2001),而且由于用数组的效率高于vector,所以这种方法获得了较好的结果。
代码:

#include <algorithm>class Solution {public:    int findTargetSumWays(vector<int>& nums, int S) {        int pre[2001],now[2001];        memset(pre,0,sizeof(pre));        pre[1000]=1;        //由于数组下标不可能为负数,这里做了个简单的映射。pre[1000]表示和为0,pre[2000]表示和为1000,pre[0]表示和为-1000。        for(int i=0;i<nums.size();i++){            for(int j=0;j<2001;j++)                if(pre[j]!=0){                    now[j+nums[i]]+=pre[j];                    now[j-nums[i]]+=pre[j];                }            for(int j=0;j<2001;j++){                pre[j]=now[j];                now[j]=0;            }        }        if(S>1000)            return 0;        else            return pre[S+1000];    }};

解法四:(500ms通过)
思路:原本笔者觉得单纯用dfs,特别是用递归的dfs估计要超时,还不如解法一的暴力法,所以一开始就没将此法放在考虑范围之内。但看了网上也有用dfs直接过的。可能原因是刚开始的vector的效率更低。
代码:

//此代码来源于网络class Solution {public:    int result;    int findTargetSumWays(vector<int>& nums, int S) {        dfs(0, 0, nums, S);        return result;    }    void dfs(int sum, int cnt, vector<int>& nums, int S) {        if(cnt == nums.size()) {            if(sum == S)                result++;            return ;        }        dfs(sum + nums[cnt], cnt + 1, nums, S);        dfs(sum - nums[cnt], cnt + 1, nums, S);    }};
1 0