力扣494. 目标和

题目来源(力扣):

https://leetcode.cn/problems/target-sum/description/

题目描述:

给你一个非负整数数组 nums 和一个整数 target 。
向数组中的每个整数前添加 '+' 或 '-' ,然后串联起所有整数,可以构造一个 表达式 :
例如,nums = [2, 1] ,可以在 2 之前添加 '+' ,在 1 之前添加 '-' ,然后串联起来得到表达式 "+2-1" 。
返回可以通过上述方法构造的、运算结果等于 target 的不同 表达式 的数目。

基本思路:

可以将问题转化为0-1背包问题,每个物品(必须取)取得其正价值或者负价值。
dp[i][j]表示前i个物品,取得价值为j时,方案数为dp[i][j]。
递推表达式:dp[i][j]=dp[i-1][j-nums[i]]+dp[i-1][j+nums[i]];
由于j可能为负,而数组下标不能为负数,因此定义时使用map而非vector;由于该map不涉及顺序问题,只是简单利用哈希性质,所以可以使用unordered_map,以优化时间效率。(map修改、访问时间复杂度为O(logn),而unordered_map为O(1))

注意遍历时,j从-sum~sum,sum为所有nums[i]的绝对值之和。
具体可见代码。

代码实现:

class Solution {
public:
    int findTargetSumWays(vector<int>& nums, int target) {
        // 0.预处理,得到累计和sum、target的绝对值
        int len = nums.size();
        int sum = 0;
        for (int i : nums) {
            if (i < 0)
                i = -i;
            sum += i;
        }
        // if(target<0)
        //     target=-target;
        // 1.定义dp数组及其含义 dp[i][j]表示前i个数组成数字j的方案为dp[i][j]
        // int dp[23][2000]={0};//由于数组下标不为负,因此需要对j进行特殊处理
        unordered_map<int, int> dp[23]; // 利用map即可

        // 2.确定递推公式
        //  dp[i][j]=dp[i-1][j-nums[i]]+dp[i-1][j+nums[i]];

        // 3.初始化 这里不直接dp[][]=1,是因为第一个可能是0,而+0与-0是2个方案。
        dp[0][nums[0]] += 1;
        dp[0][-nums[0]] += 1;

        // 4.确定遍历顺序 从前往后
        for (int i = 1; i < len; i++) {
            for (int j = -sum; j <= sum; j++) {
                dp[i][j] = dp[i - 1][j - nums[i]] + dp[i - 1][j + nums[i]];
            }
        }

        // 5.打印部分数据以确定是否符合预期
        //  for(int i=0;i<len;i++){
        //      for(int j=-sum;j<=sum;j++)
        //          cout<<dp[i][j]<<" ";
        //      cout<<endl;
        //  }
        //  cout<<endl<<sum<<endl;
        //  cout<<target<<endl;
        return dp[len - 1][target];
    }
};

时间复杂度

O(n*sum) ,其中n为nums中整数的个数,sum为所有整数的绝对值之和.

补充-方法2

此题可以利用数学方式使得代码变得简洁,参考《代码随想录》,如下:
对原数据分为2组,left和right,target为目标值,sum为所有数的绝对值之和,则:
left-right=target
left+right=sum
可以推出left=(target+sum)/2.
target和sum都已知,因此只需要判断从sum数组中选出若干个数,使得它们的值为left=(target+sum)/2,从而转换为一个更加简单的0-1背包问题。
注意此处的思路、dp数组的含义、dp初始化等与刚才的方法完全不同。
代码如下:

class Solution {
public:
    int findTargetSumWays(vector<int>& nums, int target) {
        // 0.预处理,得到累计和sum、target的绝对值
        int len = nums.size();
        int sum = 0;
        for (int i : nums) {
            if (i < 0)
                i = -i;
            sum += i;
        }
        if(target>sum||-target>sum)return 0;
        if((target+sum)%2==1)return 0;
        int left=(target+sum)/2;
        // 1.定义dp数组及其含义 dp[i][j]表示前i个数组成数字j的方案为dp[j]
        //dp[j]表示组成j的方案为dp[j]
        vector<int>dp(left+1);  //dp[]

        // 2.确定递推公式
        //  dp[j]+=dp[j-nums[i]];

        // 3.初始化
        dp[0]=1;

        // 4.确定遍历顺序 从前往后
        for (int i = 0; i < len; i++) {
            for (int j = left; j >= nums[i]; j--) {
                dp[j]+=dp[j-nums[i]];
            }
        }

        // 5.打印部分数据以确定是否符合预期
        // for(int j=0;j<=left;j++)
        //     cout<<dp[j]<<" ";
        // cout<<endl;
        return dp[left];
    }
};

时间复杂度2

时间复杂度: O(n*left),其中n为nums中整数的个数,left=为所有整数的绝对值之和+目标值target

posted @ 2024-12-12 23:30  HB_Computer  阅读(38)  评论(0)    收藏  举报