首页 > 试题广场 >

目标和

[编程题]目标和
  • 热度指数:2482 时间限制:C/C++ 1秒,其他语言2秒 空间限制:C/C++ 256M,其他语言512M
  • 算法知识视频讲解
给定一个整数数组nums和一个整数target,请你返回该数组能构成多少种不同的表达式等于target。
规则如下:
1.将数组里每个整数前面可以添加"+"或者"-"符号,组成一个表达式,例如[1,2],可以变成”+1+2","+1-2","-1+2","-1-2",这四种
2.只能添加"+"与"-"符号,不能添加其他的符号
3.如果构不成等于target的表达式,请返回0
4.保证返回的结果个数在整数范围内

数据范围:





示例1

输入

[1,1,1,2],3

输出

3

说明

-1 + 1 + 1 + 2 = 3
+1 - 1 + 1 + 2 = 3
+1 + 1 - 1 + 2 = 3
示例2

输入

[2],2

输出

1
public int findTargetSumWays (int[] nums, int target) {
        if(nums.length==0)return 0;
        findT(nums,0,target);
        return n;
    }
    
    void findT(int[] nums,int i,int s){
        if(i==nums.length){
            if(s==0)n++;
            return;
        }else {
            s+=(-1)*nums[i];
            findT(nums,i+1,s);
            s-=(-1)*nums[i];
            s+=nums[i];
            findT(nums,i+1,s);
            s-=nums[i];
        }
    }

发表于 2022-05-20 19:30:40 回复(0)
从左往右的尝试模型,数据量很小,直接暴力递归能过。

暴力递归

每一层递归可以选择对当前数添加或不添加负号,将其累加到结果上,直到凑出target,就生成一种方法。
import java.util.*;


public class Solution {
    /**
     * 代码中的类名、方法名、参数名已经指定,请勿修改,直接返回方法规定的值即可
     *
     * 
     * @param nums int整型一维数组 
     * @param target int整型 
     * @return int整型
     */
    public int findTargetSumWays (int[] nums, int target) {
        // write code here
        if(nums == null || nums.length == 0) return 0;
        return dfs(nums, 0, target);
    }
    
    private int dfs(int[] nums, int index, int rest) {
        if(index == nums.length){
            return rest == 0? 1: 0;     // 到最后一个数如果rest为0了,就形成了一种合法的方案
        }
        // 否则就给当前的数加上负号或不加负号累加到rest上,继续尝试下面的数
        return dfs(nums, index + 1, rest + nums[index]) + dfs(nums, index + 1, rest - nums[index]);
    }
}

记忆化搜索

根据递归逻辑可以用缓存法改出一版记忆化搜索,避免重复的递归展开计算。理论上来说,记忆化搜索的速度应该是更快的,在leetcode上把这一题改成记忆化搜索后, 可以快9倍左右,但是牛客上可能数据量比较小,反而更慢了。
import java.util.*;


public class Solution {
    /**
     * 代码中的类名、方法名、参数名已经指定,请勿修改,直接返回方法规定的值即可
     *
     * 
     * @param nums int整型一维数组 
     * @param target int整型 
     * @return int整型
     */
    public int findTargetSumWays (int[] nums, int target) {
        // write code here
        if(nums == null || nums.length == 0) return 0;
        int n = nums.length;
        HashMap<Integer, HashMap<Integer, Integer>> mem = new HashMap<>();
        return dfs(nums, 0, target, mem);
    }
    
    private int dfs(int[] nums, int index, int rest, HashMap<Integer, HashMap<Integer, Integer>> mem) {
        if(mem.containsKey(index) && mem.get(index).containsKey(rest)) {
            return mem.get(index).get(rest);    // 缓存命中,直接返回
        }
        if(index == nums.length){
            return rest == 0? 1: 0;     // 到最后一个数如果rest为0了,就形成了一种合法的方案
        }
        HashMap<Integer, Integer> map = new HashMap<>();
        // 否则就给当前的数加上负号或不加负号累加到rest上,继续尝试下面的数
        int res = dfs(nums, index + 1, rest + nums[index], mem) + dfs(nums, index + 1, rest - nums[index], mem);
        map.put(rest, res);
        mem.put(index, map);
        return res;
    }
}

动态规划

这个题如果直接根据记忆化搜索的递归逻辑改成动态规划是没什么收益的,考虑到rest会取到负数,缓存无论如何都需要使用哈希表,这无疑就增加了取值的常数时间。这样改出来的动态规划没有任何优势。
因此我们需要分析一下业务:记原始数组所有元素的累加和为sum,假设我们对所有数添加完正负号之后凑出了目标和target,此时数组中有P个正数,N个负数。则有如下两式成立


联立两式得到,这时候问题就转化为:从数组中选择若干数,能凑出P的方案数,成为了一个纯背包问题。同时还有两个点可以优化常数时间:(1)如果sum比target的绝对值还小,肯定没有凑数方法。(2)如果sum和target的奇偶性不同,肯定也没有凑数方法。
import java.util.*;


public class Solution {
    /**
     * 代码中的类名、方法名、参数名已经指定,请勿修改,直接返回方法规定的值即可
     *
     * 
     * @param nums int整型一维数组 
     * @param target int整型 
     * @return int整型
     */
    public int findTargetSumWays (int[] nums, int target) {
        // write code here
        if(nums == null || nums.length == 0) return 0;
        int sum = 0;
        for(int i = 0; i < nums.length; i++){
            sum += nums[i];
        }
        if(sum < Math.abs(target) || ((sum + target) & 1) != 0) {
            // sum不足target的绝对值,或sum和target奇偶性不同,直接返回0
            return 0;
        }
        int upBound = (sum + target) >> 1;
        int[] dp = new int[upBound + 1];
        dp[0] = 1;    // base case凑出0有1种方案:什么数都不选
        for(int num: nums){
            for(int i = upBound; i >= num; i--){
                dp[i] += dp[i - num];
            }
        }
        return dp[upBound];
    }
}

编辑于 2022-01-05 09:30:35 回复(0)
public int findTargetSumWays(int[] nums, int target) {
        if(nums.length==0) return 0;
        solve(0, nums, target);
        return ncount;
    }
    int ncount = 0;
    public void solve(int idx, int[] nums, int target) {
        if (idx == nums.length && target == 0) {
            ncount++;
            return;
        }
        if (idx == nums.length) return;
        solve(idx+1, nums, target-nums[idx]); // nums[idx]符号为正
        solve(idx+1, nums, target+nums[idx]); // nums[idx]符号为负
    }

发表于 2021-12-28 11:06:02 回复(0)