-
Notifications
You must be signed in to change notification settings - Fork 4
/
Copy pathtarget-sum.py
166 lines (151 loc) · 5.45 KB
/
target-sum.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
# 494. Target Sum
# 🟠 Medium
#
# https://leetcode.com/problems/target-sum/
#
# Tags: Array - Dynamic Programming - Backtracking
import timeit
from collections import Counter
from functools import cache
from typing import List
# Top down memoization solution using a dictionary as the memo object.
# each calls shifts the index one position forward and returns the
# result of the recursive call adding both the positive and negated
# value under the current pointer to the result.
#
# Time complexity: O(n^2) - We are avoiding calculating results that we
# have already stored in the memo, we will compute results for the same
# index and sum only once, there are approximately n^2 over the size of
# the nums input combinations of index and sum.
# Space complexity: O(n^2) - The memo will have one call for each of the
# possible combinations of index and current sum.
#
# Runtime: 973 ms, faster than 29.34%
# Memory Usage: 41.7 MB, less than 9.68%
class Memoization:
def findTargetSumWays(self, nums: List[int], target: int) -> int:
memo = {}
def helper(i: int, sum: int) -> int:
key = (i, sum)
# Check the memo.
if key in memo:
return memo[key]
# Stop this branch when we reach a leaf.
if i == len(nums):
memo[key] = int(sum == target)
return memo[key]
# Visit both branches.
memo[key] = helper(i + 1, sum - nums[i]) + helper(
i + 1, sum + nums[i]
)
return memo[key]
return helper(0, 0)
# Similar solution to the memoization one but using functools cache
# instead of a manually managed dictionary.
#
# Time complexity: O(n^2)
# Space complexity: O(n^2)
#
# Runtime: 283 ms, faster than 88.90%
# Memory Usage: 41.8 MB, less than 8.12%
class MemoizationCache:
def findTargetSumWays(self, nums: List[int], target: int) -> int:
@cache
def helper(i: int, sum: int) -> int:
# Stop this branch when we reach a leaf.
if i == len(nums):
return int(sum == target)
# Visit both branches.
return helper(i + 1, sum - nums[i]) + helper(i + 1, sum + nums[i])
return helper(0, 0)
# 2D Dynamic programming solution. We could use a 2D array of size n*t
# where n is the number of elements in the array nums and t is all the
# possible values that the sum could take, that is all the values
# between [-sum(nums), sum(nums)], each row represents the index of nums
# that we are visiting and each column represents a value, the cells
# represent how many different ways we have of arriving at that value.
# On each iteration, we use all the values on the previous row to
# compute the ways to calculate the values on the current row given the
# current value nums[i], we would return the value at index target on
# the last row.
# We can optimize the space usage of the proposed 2D Dynamic programming
# solution if we keep a dictionary of value: ways to sum up to it, then
# iterate over the items in nums, for each key in the dictionary, which
# represent a value that we could sum up on the previous iteration, we
# add its count to two values in the new dictionary, key+nums[i] and
# key-nums[i] because we could choose to add or subtract this value,
# then we update the dp dictionary with the values just computed before
# visiting the next index. The result is the value at index target after
# we visit all indexes.
#
# Time complexity: O(n*t) - Where n is the number of values in the input
# array nums and t is the number of values that the sum can take, or
# [-sum(nums)...sum(nums)].
# Space complexity: O(t) - Where t is the number of values that the sum
# can take, all values in [-sum(nums)...sum(nums)].
#
# Runtime: 304 ms, faster than 87.41%
# Memory Usage: 14.1 MB, less than 88.17%
class BottomUpLinkedIn:
def findTargetSumWays(self, nums: List[int], target: int) -> int:
dp = Counter([0])
for num in nums:
next = Counter()
for key in dp.keys():
next[key - num] += dp[key]
next[key + num] += dp[key]
dp = next
return dp[target]
def test():
executors = [
Memoization,
MemoizationCache,
BottomUpLinkedIn,
]
tests = [
[[1], 1, 1],
[[1, 1, 1, 1, 1], 3, 5],
[
[
25,
33,
27,
23,
46,
16,
10,
27,
33,
2,
12,
2,
29,
44,
49,
40,
32,
46,
7,
50,
],
4,
0,
],
]
for executor in executors:
start = timeit.default_timer()
for _ in range(1):
for col, t in enumerate(tests):
sol = executor()
result = sol.findTargetSumWays(t[0], t[1])
exp = t[2]
assert result == exp, (
f"\033[93m» {result} <> {exp}\033[91m for"
+ f" test {col} using \033[1m{executor.__name__}"
)
stop = timeit.default_timer()
used = str(round(stop - start, 5))
cols = "{0:20}{1:10}{2:10}"
res = cols.format(executor.__name__, used, "seconds")
print(f"\033[92m» {res}\033[0m")
test()