目标和 动态规划

1. 题目描述

给定一个非负整数数组,a1, a2, ..., an,和一个目标数S。现在你有两个符号+-。对于数组中的任意一个整数,你都可以从+-中选择一个符号添加在前面。
返回可以使最终数组和为目标数S的所有添加符号的方案数。
提示:

  • 数组非空,且长度不会超过20
  • 初始的数组的和不会超过1000
  • 保证返回的最终结果能被32位整数存下。

示例:

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

2. 题解

这道题是一个常见的背包问题,我们可以用类似求解背包问题的方法来求出可能的方案数。
我们用dp[i][j]表示用数组中的前i个元素,组成和为j的方案数。

public int findTargetSumWays(int[] nums, int S) {
	int[][] dp = new int[nums.length][2001];
	dp[0][nums[0] + 1000] = 1;
	dp[0][-nums[0] + 1000] += 1;
	for (int i = 1; i < nums.length; i++) {
		for (int sum = -1000; sum <= 1000; sum++) {
			if (dp[i - 1][sum + 1000] > 0) {
				dp[i][sum + nums[i] + 1000] += dp[i - 1][sum + 1000];
				dp[i][sum - nums[i] + 1000] += dp[i - 1][sum + 1000];
			}
		}
	}
	return S > 1000 ? 0 : dp[nums.length - 1][S + 1000];
}

注意到数组下标不能为负数,由于题目要求数组和不超过1000,所以需要给dp[i][j]的第二维预先增加1000

nums: [2, 1, 1, 2], S: 0为例,这里给dp[i][j]的第二维预先增加6,因为该数组和不超过6

public int findTargetSumWays(int[] nums, int S) {
	int[][] dp = new int[nums.length][13];
	dp[0][nums[0] + 6] = 1;
	dp[0][-nums[0] + 6] += 1;
	for (int i = 1; i < nums.length; i++) {
		for (int sum = -6; sum <= 6; sum++) {
			if (dp[i - 1][sum + 6] > 0) {
				dp[i][sum + nums[i] + 6] += dp[i - 1][sum + 6];
				dp[i][sum - nums[i] + 6] += dp[i - 1][sum + 6];
			}
		}
	}
	return S > 6 ? 0 : dp[nums.length - 1][S + 6];
}


dp[0][2 + 6]表示用数组中的前0个元素,组成和为8的方案数。因为dp[i][j]的第二维预先增加6,所以实际上dp[0][2 + 6]表示用数组中的前0个元素,组成和为2的方案数。
因此,这里dp[3][6]表示用数组中的前3个元素(包含4个元素),组成和为0的方案数,即dp[3][0 + 6]
解释代码:
nums[0]+nums[0]-nums[0]两种情况,接着遍历nums[1]
由于dp[0][-2 + 6] = 1 > 0,所以dp[1][-2 + 1 + 6] += dp[0][-2 + 6]dp[1][-2 - 1 + 6] += dp[0][-2 + 6]
接着遍历nums[2],同理,dp[2][-3 + 1 + 6] += dp[1][-3 + 6]dp[2][-1 - 1 + 6] += dp[1][-1 + 6]
dp[2][-2 + 6]表示用数组中的前2个元素,组成和为-2的方案数为2(-2+1-1-2-1+1)。
这里dp[2][-2 + 6]先加dp[1][-3 + 6],再加dp[1][-1 + 6],所以要用+=来累加方案数。

参考:

posted @ 2020-12-22 11:14  gzhjj  阅读(162)  评论(0编辑  收藏  举报