Leetcode/目标和

给你一个整数数组 nums 和一个整数 target 。
向数组中的每个整数前添加 '+' 或 '-' ,然后串联起所有整数,可以构造一个表达式
返回可以通过上述方法构造的、运算结果等于target的不同表达式的数目

1. 回溯法(全遍历)

public:
    int res = 0;
    int findTargetSumWays(vector<int>& nums, int target) {
        backtrack(nums,0,target);
        return res;
    }
    
    void backtrack(vector<int>& nums,int i,int target){
        if(i==nums.size()&&target==0){ res++; return;}
        if(i==nums.size()) return;
        backtrack(nums,i+1,target-nums[i]);
        backtrack(nums,i+1,target+nums[i]);
    }
};
反向递归
class Solution {
public:
    int res = 0;
    int findTargetSumWays(vector<int>& nums, int target) {
        backtrack(nums,nums.size()-1,target);
        return res;
    }

    void backtrack(vector<int>& nums,int i,int target){
        if(i==-1&&target==0){ res++; return;}
        if(i==-1) return;
        backtrack(nums,i-1,target-nums[i]);
        backtrack(nums,i-1,target+nums[i]);
    }
};

2. 记忆化搜索

可以在方法一的基础上记录指定位置在当前目标值下的方法数
用备忘录的方法减少重复遍历,与青蛙跳跃那题一致,但性能提升不大

记忆化搜索
class Solution {
public:
    vector<map<int,int>>  memo;//备忘录存储对应下标,指定目标值的解法,减少重复遍历
    int findTargetSumWays(vector<int>& nums, int target) {
        memo.resize(nums.size());
       return backtrack(nums,0,target);
    }
    int backtrack(vector<int>& nums, int i,int target){
        if(i==nums.size()){
            if(target==0) return 1;
            return 0;
        }
        if(memo[i].count(target)) return memo[i][target];//这一句在判定之后,否则超出内存
        int left = backtrack(nums,i+1,target+nums[i]);
        int right = backtrack(nums,i+1,target-nums[i]);
        return memo[i][target]=left+right;
    }
};

3. 动态规划

原问题乍一看无法转换为动态规划问题,因为每位都必须选择正负,不能不做选择
而且各位的数值不一样,假如dp[i]表示为凑成总和i的方案数显然不满足题意,无法得到状态转移方程
如果用dp[k][i]表示前k位凑成总和i的方案数,所需的空间复杂度过于庞大,很多冗余无效数据,导致处理时间性能不如直接回溯全遍历
这里要先对数据进行处理,将问题进行转化

记数组元素和为sum,添加-号元素和为neg,容易得到neg=(sum-targe)/2

这样我们就可以把问题转换成选取元素使得和为neg,和选取有限硬币得到金额是一样的问题,其实就是01背包问题
dp[i][j]表示数组nums的前i个数中选取元素,使得这些元素之和等于j的方案数
dp[i][j]=dp[i-1][j]+dp[i-1][j-nums[i]]
边界条件dp[0][i]=0,dp[0][0]=1

二维动态规划
class Solution {
public:
    int findTargetSumWays(vector<int>& nums, int target) {
        int sum = 0;
        for (int& num : nums) sum += num;
        int diff = sum - target;
        if (diff < 0 || diff % 2 != 0)  return 0;
        int n = nums.size(), neg = diff / 2;
        vector<vector<int>> dp(n + 1, vector<int>(neg + 1));
        dp[0][0] = 1;
        for (int i = 1; i <= n; i++) 
            for (int j = 0; j <= neg; j++) {
                dp[i][j] = dp[i - 1][j];
                if (j >= nums[i - 1]) 
                    dp[i][j] += dp[i - 1][j - nums[i - 1]];
            }
        return dp[n][neg];
    }
};

一维优化 这里内循环必须从后往前,因为后面元素要用到上一行前面位置元素,从前往后会被覆盖掉
class Solution {
public:
    int findTargetSumWays(vector<int>& nums, int target) {
        int sum = 0;
        for (int& num : nums) {
            sum += num;
        }
        int diff = sum - target;
        if (diff < 0 || diff % 2 != 0) {
            return 0;
        }
        int neg = diff / 2;
        vector<int> dp(neg + 1);
        dp[0] = 1;
        for (int& num : nums) {
            for (int j = neg; j >= num; j--) {
                dp[j] += dp[j - num];
            }
        }
        return dp[neg];
    }
};
posted @ 2022-05-22 17:30  失控D大白兔  阅读(42)  评论(0编辑  收藏  举报