打造目标和:从leetcode 494题中获取借鉴
2024-02-15 20:17:51
494题——目标和
我们来思考一下,对于原数组nums
来说,为了得到目标和target
,有哪些方式可以达成呢?有且仅有两种方式:
- 选中当前元素,为其加上正号,然后将目标和减去该元素的值。
- 选中当前元素,为其加上负号,然后将目标和减去该元素的值。
注意到,在前面的讨论中,选择或者不选择当前元素都是唯一的,因此我们可以选择以动态规划的方式来解决这个问题。我们先用一个二维数组dp
来记录当前的子数组能得到的和的种类是否包含target。其状态方程为:
即,当前子数组的和等于目标和的种类是否包含target,只和当前子数组的前一个子数组的和是否包含target有关。其边界条件为:
接下来我们来证明,这个状态方程和边界条件都是成立的。首先是边界条件,显然,空数组的和只能为0,因此0的和只包含0,对于空数组,0的和包含target显然是成立的。我们假设,对于子数组i-1,状态方程和边界条件是成立的,现在我们来考虑当前的子数组i。如果包含target,显然,当前子数组的和包含target。那么,如果前一个子数组的和不包含target,那么当前子数组的和也不包含target。我们来证明,对于当前子数组的和包含target,一定有其前一个子数组的和包含target。由于target已经不为0了,当前子数组的和包含target,只能有两种可能,选中当前元素,为其加上正号,然后将目标和减去该元素的值,或者选中当前元素,为其加上负号,然后将目标和减去该元素的值。对于第一种情况,前一个子数组的和等于现在的和加上元素nums[i]
,由于当前子数组的和等于target,所以前一个子数组的和就等于target-nums[i]
,所以前一个子数组的和包含target,同理,对于第二种情况,前一个子数组的和也包含target。
因此,动态规划的解法是成立的。
我们来看一看动态规划的代码:
class Solution:
def findTargetSumWays(self, nums, target):
n = len(nums)
dp = [[False]*(2*sum(nums)+1) for _ in range(n+1)]
dp[0][sum(nums)] = True
for i in range(1,n+1):
for j in range(-sum(nums),sum(nums)+1):
dp[i][j+sum(nums)] |= (dp[i-1][j-nums[i-1]] or dp[i-1][j+nums[i-1]])
return dp[n][target+sum(nums)]
其时间复杂度为O(nsum),空间复杂度为O(nsum)。
除了动态规划,还有哪些方式可以解决这个问题呢?其实,除了动态规划,我们还可以用回溯的方式来解决这个问题。
回到前面的讨论,我们说到,想要让当前子数组的和等于target,只和当前子数组的前一个子数组的和有关。如果把这个过程看成是一个树,那么每次可以有两种选择,即选择当前元素,为其加上正号,然后将目标和减去该元素的值,或者选择当前元素,为其加上负号,然后将目标和减去该元素的值。这样就把这个问题转化成了一个树的遍历问题。我们可以在树中进行深度优先搜索,当找到一条路径,使得当前路径上的所有元素的和等于target的时候,我们就找到了一种方法。
我们来看一看回溯的代码:
class Solution:
def findTargetSumWays(self, nums, target):
res = 0
def dfs(i,cur_sum):
nonlocal res
if i==len(nums):
if cur_sum==target:
res += 1
return
dfs(i+1,cur_sum+nums[i])
dfs(i+1,cur_sum-nums[i])
dfs(0,0)
return res
其时间复杂度为O(2^n),空间复杂度为O(n)。
以上就是解决leetcode 494题的方法。