【动态规划】LeetCode - 494. 目标和——回溯法、动态规划

494. 目标和

题目描述

给定一个非负整数数组,a1, a2, …, an, 和一个目标数,S。现在你有两个符号 + 和 -。对于数组中的任意一个整数,你都可以从 + 或 -中选择一个符号添加在前面。

返回可以使最终数组和为目标数 S 的所有添加符号的方法数。

示例:

输入: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。

解题思路

回溯法

static int result = 0;
    /**
     * 回溯法
     */
    static int findTargetSumWays_1(int[] nums, int target){
        if (nums.length == 0)
            return 0;
        backtrack(nums,0,target);
        return result;
    }

    private static void backtrack(int[] nums, int i, int rest) {
        //base case
        if(i == nums.length){
            if (rest == 0){
                //说明恰好凑出target
                result++;
            }
            return;
        }
        //给nums[i] 选择 - 号
        rest += nums[i];
        //穷举nums[i+1]
        backtrack(nums, i+1, rest);
        //撤销选择
        rest -= nums[i];

        //给nums[i]选择 + 号
        rest -= nums[i];
        //穷举nums[i+1]
        backtrack(nums,i+1,rest);
        //撤销选择
        rest += nums[i];
    }

消除重复子问题

public static int findTargetSumWays_2(int[] nums,int target){
        if (nums.length == 0)
            return 0;
        return dp(nums,0,target);
    }

    //备忘录
    private static HashMap<String,Integer> memo = new HashMap<>();
    private static int dp(int[] nums, int i, int rest) {
        //base case
        if(i == nums.length){
            if (rest == 0)
                return 1;
            return 0;
        }
        //把它们转成字符串才能作为哈希表的键
        String key = i + "," + rest;
        System.out.println(key);
        //避免重复计算
        if (memo.containsKey(key)){
            return memo.get(key);
        }
        //还是穷举
        int result = dp(nums,i + 1,rest - nums[i]) + dp(nums,i+1,rest + nums[i]);
        //记入备忘录
        memo.put(key,result);
        System.out.println(memo.toString());
        return result;
    }

动态规划

   public static int findTargetSumWays_3(int[] nums,int target){
         int sum = 0;
         for (int n : nums) {
             sum += n;
         }
         //这两种情况,不可能存在合法的子集划分
         if (sum < target || (sum + target) % 2 == 1){
             return 0;
         }
         return subsets(nums,(sum + target) / 2);
     }

     //状态转移方程:dp[i][j] = dp[i - 1][j] + dp[i - 1][j - nums[i - 1]]
    private static int subsets(int[] nums, int sum) {
        int n = nums.length;
        int[][] dp = new int[n + 1][sum + 1];
        //base case
        for (int i = 0; i <= n; i++) {
            dp[i][0] = 1;
        }
        for (int i = 1; i <= n; i++) {
            for (int j = 0; j <= sum; j++) {
                if (j >= nums[i - 1]){
                    //两种选择的结果之和
                    dp[i][j] = dp[i - 1][j] + dp[i - 1][j - nums[i - 1]];
                }else {
                    //背包的空间不足,只能选择不装物品
                    dp[i][j] = dp[i - 1][j];
                }
            }

        }
         return dp[n][sum];
    }

一维状态的动态规划

   public static int findTargetSumWays(int[] nums,int target) {
        int sum = 0;
        for (int n : nums) {
            sum += n;
        }
        //这两种情况,不可能存在合法的子集划分
        if (sum < target || (sum + target) % 2 == 1){
            return 0;
        }
        return subsets_1(nums,(sum + target) / 2);
    }

    private static int subsets_1(int[] nums, int sum) {
        int n = nums.length;
        int[] dp = new int[sum + 1];
        //base case
        dp[0] = 1;
        for (int i = 1;i <= n;i++){
            //j要从后往前遍历
            for (int j = sum;j >= 0;j--){
                //状态转移方程
                if (j >= nums[i - 1]){
                    dp[j] = dp[j] + dp[j - nums[i - 1]];
                }else {
                    dp[j] = dp[j];
                }
            }
        }
         return dp[sum];
    }
posted @ 2021-04-01 20:02  your_棒棒糖  阅读(90)  评论(0)    收藏  举报