The trick here is to name the state correctly, then let the implementation follow. For 494. Target Sum, the solution in this repository is mainly a dynamic programming solution.
Guide
What?
We want to turn the problem statement into a smaller set of decisions the computer can repeat safely. Instead of trying to be clever immediately, read the code as a sequence of questions:
- What state are we keeping?
- How do we move from one state to the next?
- When do we know the answer is already determined?
For this file, the main tools are: dynamic programming.
The notes already sitting in the source point us in the right direction:
- recursion
- time: O(2^N), space: O(N)
Guide
When?
This is the kind of solution you want when the problem has structure hiding inside a messy-looking input. The accepted code reduces that pressure by storing exactly the information that remains useful later.
The important function names to track are findTargetSumWays.
Guide
Why?
The solution works because it narrows the problem until every update has a clear reason to exist.
- The two-dimensional vector is the memory of the solution: grid state, DP state, or adjacency shape.
- The final return is not magic; it is the invariant after the loops or recursion have finished doing their accounting.
Guide
How?
Walk through the solution in this order:
- Read the setup variables first.
- Follow the main loop or recursive helper next.
- Watch where invalid states get skipped.
- Check which value survives to the return statement.
The most important competitive-programming habit here is to trust the invariant. Once the invariant is right, the loops become much less scary.
Guide
Complexity
- Time: O(sum*N), space: O(sum*N)
- Space: O(n) in the usual case for auxiliary containers or recursion
Guide
C++ Solution
Your submission
The accepted solution
01//recursion
02//Runtime: 1452 ms, faster than 10.35% of C++ online submissions for Target Sum.
03//Memory Usage: 9.2 MB, less than 38.46% of C++ online submissions for Target Sum.
04//time: O(2^N), space: O(N)
05class Solution {
06public:
07 int findTargetSumWays(vector<int>& nums, int S, int start) {
08 if(start == nums.size() || S == INT_MAX) return S == 0;
09
10 //nums[i] >= 0 for all i
11 //sum of nums <= 1000
12 //add this determination to avoid overflow
13 return findTargetSumWays(nums, S <= 1000 ? S+nums[start] : INT_MAX, start+1) +
14 findTargetSumWays(nums, S-nums[start], start+1);
15 }
16
17 int findTargetSumWays(vector<int>& nums, int S) {
18 return findTargetSumWays(nums, S, 0);
19 }
20};
21
22//recursion + memorization
23//Runtime: 20 ms, faster than 75.18% of C++ online submissions for Target Sum.
24//Memory Usage: 38.2 MB, less than 23.08% of C++ online submissions for Target Sum.
25//time: O(sum*N), space: O(sum*N)
26class Solution {
27public:
28 vector<vector<int>> memo;
29
30 int findTargetSumWays(vector<int>& nums, int S, int start) {
31 /*
32 change the condition to S > 1000, so that
33 memo[start][S+1000] is always valid
34 */
35 if(start == nums.size() || S > 1000) return S == 0;
36
37 if(memo[start][S+1000] != INT_MAX) return memo[start][S+1000];
38
39 // cout << S << endl;
40 //nums[i] >= 0 for all i
41 //sum of nums <= 1000
42 //add this determination to avoid overflow
43 memo[start][S+1000] = findTargetSumWays(nums, S <= 1000 ? S+nums[start] : INT_MAX, start+1) +
44 findTargetSumWays(nums, S-nums[start], start+1);
45
46 return memo[start][S+1000];
47 }
48
49 int findTargetSumWays(vector<int>& nums, int S) {
50 /*
51 sum of nums <= 1000,
52 so the possible range after adding + or - is [-1000, 1000],
53 we map [-1000,1000] to [0,2000] as the index of memo
54 */
55 memo = vector<vector<int>>(nums.size(), vector(2001, INT_MAX));
56 findTargetSumWays(nums, S, 0);
57 //if S > 1000, it's impossible for us to make up S by nums
58 return S <= 1000 ? memo[0][S+1000] : 0;
59 }
60};
61
62//2D DP
63//Runtime: 32 ms, faster than 66.46% of C++ online submissions for Target Sum.
64//Memory Usage: 38.2 MB, less than 23.08% of C++ online submissions for Target Sum.
65//time: O(sum*N), space: O(sum*N)
66class Solution {
67public:
68 int findTargetSumWays(vector<int>& nums, int S) {
69 int n = nums.size();
70 /*
71 dp[i][j]: sum start from nums[i], sum to j, the count is dp[i][j]
72 */
73 vector<vector<int>> dp(n, vector(2001, 0));
74
75 //base case
76 dp[n-1][nums[n-1]+1000] = 1;
77 /*
78 here we should use += !!!
79 because nums[n-1] may be 0!!!
80 */
81 dp[n-1][-nums[n-1]+1000] += 1;
82
83 for(int i = n-2; i >= 0; i--){
84 //try all possible sum!
85 for(int sum = -1000; sum <= 1000; sum++){
86 if(dp[i+1][sum+1000] > 0){
87 dp[i][sum+nums[i]+1000] += dp[i+1][sum+1000];
88 dp[i][sum-nums[i]+1000] += dp[i+1][sum+1000];
89 // cout << "(" << i << ", " << sum+nums[i] << "): " << dp[i][sum+nums[i]+1000] << endl;
90 // cout << "(" << i << ", " << sum-nums[i] << "): " << dp[i][sum-nums[i]+1000] << endl;
91 }
92 }
93 }
94 return (S <= 1000) ? dp[0][S+1000] : 0;
95 }
96};
97
98//DP, O(N) space
99//Runtime: 56 ms, faster than 60.66% of C++ online submissions for Target Sum.
100//Memory Usage: 13.7 MB, less than 38.46% of C++ online submissions for Target Sum.
101class Solution {
102public:
103 int findTargetSumWays(vector<int>& nums, int S) {
104 int n = nums.size();
105 vector<vector<int>> dp(2, vector(2001, 0));
106
107 //base case
108 dp[(n-1)%2][nums[n-1]+1000] = 1;
109 dp[(n-1)%2][-nums[n-1]+1000] += 1;
110
111 for(int i = n-2; i >= 0; i--){
112 for(int sum = -1000; sum <= 1000; sum++){
113 if(dp[(i+1)%2][sum+1000] > 0){
114 dp[i%2][sum+nums[i]+1000] += dp[(i+1)%2][sum+1000];
115 dp[i%2][sum-nums[i]+1000] += dp[(i+1)%2][sum+1000];
116 }
117 //need to reset it, it will be used in next iteration
118 dp[(i+1)%2][sum+1000] = 0;
119 }
120 }
121 return (S <= 1000) ? dp[0][S+1000] : 0;
122 }
123};
Cost