目標とリートコード (494)
トピックの説明
整数配列 nums と整数ターゲットが与えられます。
配列内の各整数に「+」または「-」を追加し、すべての整数を連結して式を作成します。
たとえば、nums = [2, 1] の場合、2 の前に「+」を追加し、1 の前に「-」を追加して、連結して式「+2-1」を取得できます。
上記のメソッドによって構築でき、ターゲットとして評価される個別の式の数を返します。
例
例1
入力: nums = [1,1,1,1,1]、ターゲット = 3
出力: 5
説明: 最終的なターゲットの合計を 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
例 2
入力: nums = [1]、ターゲット = 1
出力: 1
制約
- 1 <= nums.length <= 20
- 0 <= nums[i] <= 1000
- 0 <= sum(nums[i]) <= 1000
- -1000 <= ターゲット <= 1000
アイデア (動的プログラミング)
アイデア 1
dp 配列は 2 次元であり、dp[i][j] は、最初の i 個の数値を組み合わせて j を生成するときに考えられるすべての種を表します。
j を構成する最初の i 数値は、最初の i - 1 数値形式 (j - nums[i]) プラス nums[i]、または最初の i - 1 数値形式 (j + nums[i]) マイナス nums と見なすことができます。 [私]
したがって、状態遷移方程式dp[i][j] = dp[i - 1][j - nums[i]] + dp[i - 1][j + nums[i]]
nums[i] の値の範囲は [0, 1000]、target の値の範囲は [-1000, 1000] です。
nums[i] = 1000 の場合、dp[i][-1000] は dp[i-1][-2000] と dp[i-1][0] で構成できます。
dp[i][1000]は、dp[i-1][0]とdp[i-1][2000]から構築できます。
つまり j の範囲は -2000 から 2000 です
配列が範囲外になるのを防ぐために、 int[][] dp = new int[nums.length][4001] とします。
コード1
class Solution {
// 0,1 背包是取和不取 这道题是 取正和取负
public int findTargetSumWays(int[] nums, int target) {
// dp的第二维 取值范围为[-2000,2000]跨度为4000
int[][] dp = new int[nums.length][4001]; // dp[i][j] 表示前i个物品组合成j+2000的可能性的种数
// 状态转移方程感觉是个递归 dp[i][j] = dp[i-1][j-nums[i]] + dp[i-1][j+nums[i]]
dp[0][nums[0] + 2000] += 1;
dp[0][-nums[0] + 2000] += 1; //因为如果是0 的话 取正取负 和为0就有两种
for (int i = 1; i < nums.length; i++) {
for (int j = nums[i]; j<=4000-nums[i]; j++) {
//这里要注意判别,防止越界
dp[i][j] = dp[i - 1][j - nums[i]] + dp[i - 1][j + nums[i]];
}
}
return dp[nums.length - 1][target + 2000];
}
}
注: コードの 8 ~ 9 行目では、= の代わりに += を使用してください。nums[i] =0 の場合、正も負も 0 になりますが、方法は 2 つあります。
アイデア 2
それぞれの番号に対して 2 つのオプションがあります。(1) 符号を正にする (2) 符号を負にする
したがって、選択に従って 2 つの山に分けることができます。
正として選択された一連の数値の合計を pos とし、負として選択された一連の数値の合計を neg とします。
すべての数値の合計が sum で、ターゲットが target です。
次の式があります:
{ neg + pos = sum ( 1 ) pos − neg = target ( 2 ) \left\{ \begin{aligned} neg +pos & = sum &(1)\\ pos - neg & = target &(2) \\ \end{aligned} \right。{
いいえ_ _+良い_ _良い_ _−いいえ_ _=うーん_ _=ターゲット_ _ _ _ _( 1 )( 2 )
用(1)+(2)得2 ∗ pos = sum + target 2 *pos = sum + target2∗良い_ _=うーん_ _+ターゲット_ _ _ _ _
したがって、この問題を、数値の束から任意の数値を取り出して、その合計が( sum + target ) / 2 (sum + target)/2 になるように変えることができます。(うーん_ _+ターゲット) / 2 . _ _ _ _ _ 方法は全部で何通りありますか?
コード2
class Solution {
public int findTargetSumWays(int[] nums, int target) {
int sum = 0;
for (int num : nums) {
sum += num;
}
if ((target + sum) % 2 != 0) return 0; // target + sum 不能被 2 整除,表示无法选数构成pos
int pos = Math.abs((sum + target) / 2);
int[] dp = new int[pos + 1]; // dp[i]表示 从中 nums中取任意个数,能构成 i 的种数
dp[0] = 1;
for (int i = 0; i < nums.length; i++) {
for (int j = pos; j >= nums[i]; j--) {
dp[j] += dp[j - nums[i]];
}
}
return dp[pos];
}
}