Leetcode第494题Target Sum动态规划部分难懂
Hard to understand the dynamic programming part of Leetcode question 494, Target Sum
我发现这个解决方案非常快(超过 99.5%),同时 space-节省(超过 95%)。
我了解大部分内容,除了以下行:dp[j]=dp[j]+dp[j-num]
我知道 w 正在计算所有带“+”号的数字的总和。
谁能解释一下这部分是什么意思? dp[j]=dp[j]+dp[j-num]
代码如下:
class Solution:
def findTargetSumWays(self, nums: List[int], S: int) -> int:
w=(S+sum(nums))//2
if (S+sum(nums))%2==1: return 0
if sum(nums)<S: return 0
dp=[0]*(w+1)
dp[0]=1
for num in nums:
for j in range(w,num-1,-1):
dp[j]=dp[j]+dp[j-num]
return dp[-1]
这里是问题:
You are given a list of non-negative integers, a1, a2, ..., an, and a target, S. Now you have 2 symbols + and -.
For each integer, you should choose one from + and - as its new symbol.
Find out how many ways to assign symbols to make sum of integers equal to target S.
Example 1:
Input: nums is [1, 1, 1, 1, 1], S is 3.
Output: 5
Explanation:
-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
There are 5 ways to assign symbols to make the sum of nums be target 3.
Sahadat 的回答是正确的,但对于 OP 来说可能不够全面。让我添加更多细节。数学等价问题是如何从列表中选择某些元素以求和为 w(符号不变)。然而,这个问题在 DP 中更容易处理。
特别地,d[0]=1 因为我们有独特的方式达到 0(通过不选择任何元素)。之后,归纳地,对于我们处理的每个 num,我们知道达到 j 的解决方案的数量是 d[j](意味着我们不选择 num)或 d[j-num](意味着我们选择 num)。一旦我们遍历列表中的所有 nums,d[w] 将包含达到 w 的解决方案的数量。这也是原题的解法
我不想尝试从理论上解释为什么会这样,我只是想遍历逻辑并描述它。
在您的示例中,nums = [1, 1, 1, 1, 1]
w=(S+sum(nums))//2 # in your example this is 4
dp=[0]*(w+1) # your array is 5 elements long, its max index is w
dp[0]=1 # initialize first index to 1, everything else is 0
for num in nums: # num will be 1 for 5 iterations
for j in range(w,num-1,-1): # j starts at the end and iterates backward.
# here j goes from index 4 to 0 every time
dp[j]=dp[j]+dp[j-num]. # remember dp = [1, 0, 0, 0, 0]. but after
# the first completion of the inner loop, dp
# will be [1, 2, 0, 0, 0]. remember you have
# negative indices in this language.
return dp[-1] # so after all iterations... dp is [1, 2, 3, 4, 5]
您只是将初始的 1 - 即 dp = [1, 0, 0, 0, 0] - 推向数组的末尾。在每次迭代中,这种情况的发生方式取决于 num is 的大小。
为什么这行得通?让我们尝试一个不同的例子,看看对比。
假设 nums = [7, 13] 并且 S 是 20。答案应该是 1,对吗? 7 + 13 = 20,这就是可能的。
w=(S+sum(nums))//2 # now this is 20.
dp=[0]*(w+1) # your array is 21 elements long, its max index is w
dp[0]=1 # initialize first index to 1, everything else is 0
for num in nums: # so num will be 7, then 13, 2 iterations
for j in range(w,num-1,-1): # j starts at the end and iterates backward.
# on first iteration j goes from index 20 to 6
# on 2nd iteration j goes from index 20 to 12
dp[j]=dp[j]+dp[j-num]. # so when num = 7, and you reach the 2nd last
# iteration of the inner loop... index 7
# you're gona set dp[7] to 1 via dp[7]+dp[0]
# subsequently, when num = 13... when you're
# at dp[20] you'll do dp[20] = d[20]+d[7].
# Thus now dp[20] = 1, which is the answer.
return dp[-1]
所以 num 越大...跳过越多,将 1 移到顶部的次数就越少。较大的 num 值分散了可以加起来达到给定 S 的可能组合。此算法说明了这一点。
但请注意最后一个示例中 dp[0] 中的 1 向上移动到 dp[20] 的方式。正是因为7+13=20,唯一的组合。这些迭代和求和说明了所有这些可能的组合。
但是算法中如何考虑潜在的符号翻转?好吧,假设之前的S不是20,而是6。即13 - 7 = 6,唯一的解决方案。好吧...让我们看看这个例子:
w=(S+sum(nums))//2 # now this is 13. (20+6)/2 = 13.
dp=[0]*(w+1) # your array is 14 elements long
dp[0]=1 # initialize first index to 1 again
for num in nums: # so num will be 7, then 13, 2 iterations
for j in range(w,num-1,-1):
# j starts at the end and iterates backward.
# on first iteration j goes from index 13 to 6
# on 2nd iteration j goes from index 13 to 12
dp[j]=dp[j]+dp[j-num].
# so this time, on the 2nd iteration, when
# num = 13... you're at dp[13]
# you'll do dp[13] = d[13]+d[0].
# Thus now dp[13] = 1, which is the answer.
# This time the 1 was moved directly from d0 to the end
return dp[-1]
所以这一次,当组合包含负数时,w 的大小为 13 导致解决方案:索引 d[13] + d[0] = 1。数组大小更小这样大的数直接加1
这很奇怪,我承认,你有 2 种不同的机制在工作,这取决于潜在的标志是什么。在正数和负数总和为 S 的情况下,较小的总数组长度导致 1 移动到最后一个槽。对于 2 个正数……这个中介将 1 向上移动了。但至少你能看出区别...
我发现这个解决方案非常快(超过 99.5%),同时 space-节省(超过 95%)。
我了解大部分内容,除了以下行:dp[j]=dp[j]+dp[j-num]
我知道 w 正在计算所有带“+”号的数字的总和。
谁能解释一下这部分是什么意思? dp[j]=dp[j]+dp[j-num]
代码如下:
class Solution:
def findTargetSumWays(self, nums: List[int], S: int) -> int:
w=(S+sum(nums))//2
if (S+sum(nums))%2==1: return 0
if sum(nums)<S: return 0
dp=[0]*(w+1)
dp[0]=1
for num in nums:
for j in range(w,num-1,-1):
dp[j]=dp[j]+dp[j-num]
return dp[-1]
这里是问题:
You are given a list of non-negative integers, a1, a2, ..., an, and a target, S. Now you have 2 symbols + and -.
For each integer, you should choose one from + and - as its new symbol.
Find out how many ways to assign symbols to make sum of integers equal to target S.
Example 1:
Input: nums is [1, 1, 1, 1, 1], S is 3.
Output: 5
Explanation:
-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
There are 5 ways to assign symbols to make the sum of nums be target 3.
Sahadat 的回答是正确的,但对于 OP 来说可能不够全面。让我添加更多细节。数学等价问题是如何从列表中选择某些元素以求和为 w(符号不变)。然而,这个问题在 DP 中更容易处理。 特别地,d[0]=1 因为我们有独特的方式达到 0(通过不选择任何元素)。之后,归纳地,对于我们处理的每个 num,我们知道达到 j 的解决方案的数量是 d[j](意味着我们不选择 num)或 d[j-num](意味着我们选择 num)。一旦我们遍历列表中的所有 nums,d[w] 将包含达到 w 的解决方案的数量。这也是原题的解法
我不想尝试从理论上解释为什么会这样,我只是想遍历逻辑并描述它。 在您的示例中,nums = [1, 1, 1, 1, 1]
w=(S+sum(nums))//2 # in your example this is 4
dp=[0]*(w+1) # your array is 5 elements long, its max index is w
dp[0]=1 # initialize first index to 1, everything else is 0
for num in nums: # num will be 1 for 5 iterations
for j in range(w,num-1,-1): # j starts at the end and iterates backward.
# here j goes from index 4 to 0 every time
dp[j]=dp[j]+dp[j-num]. # remember dp = [1, 0, 0, 0, 0]. but after
# the first completion of the inner loop, dp
# will be [1, 2, 0, 0, 0]. remember you have
# negative indices in this language.
return dp[-1] # so after all iterations... dp is [1, 2, 3, 4, 5]
您只是将初始的 1 - 即 dp = [1, 0, 0, 0, 0] - 推向数组的末尾。在每次迭代中,这种情况的发生方式取决于 num is 的大小。
为什么这行得通?让我们尝试一个不同的例子,看看对比。 假设 nums = [7, 13] 并且 S 是 20。答案应该是 1,对吗? 7 + 13 = 20,这就是可能的。
w=(S+sum(nums))//2 # now this is 20.
dp=[0]*(w+1) # your array is 21 elements long, its max index is w
dp[0]=1 # initialize first index to 1, everything else is 0
for num in nums: # so num will be 7, then 13, 2 iterations
for j in range(w,num-1,-1): # j starts at the end and iterates backward.
# on first iteration j goes from index 20 to 6
# on 2nd iteration j goes from index 20 to 12
dp[j]=dp[j]+dp[j-num]. # so when num = 7, and you reach the 2nd last
# iteration of the inner loop... index 7
# you're gona set dp[7] to 1 via dp[7]+dp[0]
# subsequently, when num = 13... when you're
# at dp[20] you'll do dp[20] = d[20]+d[7].
# Thus now dp[20] = 1, which is the answer.
return dp[-1]
所以 num 越大...跳过越多,将 1 移到顶部的次数就越少。较大的 num 值分散了可以加起来达到给定 S 的可能组合。此算法说明了这一点。 但请注意最后一个示例中 dp[0] 中的 1 向上移动到 dp[20] 的方式。正是因为7+13=20,唯一的组合。这些迭代和求和说明了所有这些可能的组合。
但是算法中如何考虑潜在的符号翻转?好吧,假设之前的S不是20,而是6。即13 - 7 = 6,唯一的解决方案。好吧...让我们看看这个例子:
w=(S+sum(nums))//2 # now this is 13. (20+6)/2 = 13.
dp=[0]*(w+1) # your array is 14 elements long
dp[0]=1 # initialize first index to 1 again
for num in nums: # so num will be 7, then 13, 2 iterations
for j in range(w,num-1,-1):
# j starts at the end and iterates backward.
# on first iteration j goes from index 13 to 6
# on 2nd iteration j goes from index 13 to 12
dp[j]=dp[j]+dp[j-num].
# so this time, on the 2nd iteration, when
# num = 13... you're at dp[13]
# you'll do dp[13] = d[13]+d[0].
# Thus now dp[13] = 1, which is the answer.
# This time the 1 was moved directly from d0 to the end
return dp[-1]
所以这一次,当组合包含负数时,w 的大小为 13 导致解决方案:索引 d[13] + d[0] = 1。数组大小更小这样大的数直接加1
这很奇怪,我承认,你有 2 种不同的机制在工作,这取决于潜在的标志是什么。在正数和负数总和为 S 的情况下,较小的总数组长度导致 1 移动到最后一个槽。对于 2 个正数……这个中介将 1 向上移动了。但至少你能看出区别...