Last Updated: March 28, 2026
We need to decide whether it is possible to split the array into two groups where both groups have the same total. The first thing to notice is that if both subsets have equal sum, then each subset's sum must be exactly half of the total array sum. If the total sum is odd, there is no way to split it evenly, so we can immediately return false.
Once we know the target is totalSum / 2, the problem transforms into something more familiar: can we find a subset of nums that adds up to exactly target? This is the classic 0/1 Knapsack / Subset Sum problem. Each number is either included in the subset or it is not, and we want to know if any combination of included numbers hits the target exactly.
1 <= nums.length <= 200 → With n up to 200, O(n^2) and even O(n sum) approaches are comfortably within time limits. Since max sum is 200 100 = 20,000, a DP table of size n * 10,000 (target = sum/2) is about 2 million entries, which is fine.1 <= nums[i] <= 100 → All values are positive, so the target sum is bounded by 10,000. This keeps the DP table manageable. Also, no zeros or negatives to worry about.The most natural starting point is to try every possible subset. For each element, we have two choices: include it in the first subset or exclude it. If at any point the running sum equals the target (totalSum / 2), we have found a valid partition.
This is essentially a depth-first search through the decision tree. At each level, we pick one element and branch into "take it" or "leave it." If the "take it" branch reaches exactly target, we return true. Otherwise, we backtrack and try the other option.
target = totalSum / 2.canFind(nums, index, remaining) that returns true if we can form a sum of remaining using elements from index index onward.remaining == 0, return true. If remaining < 0 or index >= nums.length, return false.nums[index] (subtract it from remaining) or excluding it (keep remaining the same).The brute force explores up to 2^n subsets, which is impossibly slow for n = 200. Many recursive calls solve the same subproblem repeatedly. What if we cached results so we never solve the same (index, remaining) pair twice?
The brute force solution recomputes the same subproblems many times. The state of each recursive call is fully described by two values: the current index and the remaining sum we need to achieve. If we have already computed whether it is possible to form remaining from elements at positions index through n-1, we can just return that stored result.
This is a classic application of memoization. We keep a 2D memo table keyed by (index, remaining). Before doing any work, we check if we have seen this state before. If so, return the cached answer. If not, compute it, store it, and return.
The memoization eliminates redundant computation. The total number of unique subproblems is bounded by n * target, where n is the array length and target is at most 10,000. Each subproblem is solved once. This converts an exponential search into a polynomial one, making the problem tractable even for the largest allowed inputs.
target = totalSum / 2.canFind(index, remaining):remaining == 0, return true.remaining < 0 or index >= n, return false.memo[index][remaining] is already computed, return it.canFind(0, target).The memo table uses O(n * target) space. But each row only depends on the row below it, so we are storing n rows when we only need one. What if we used a bottom-up approach with a single 1D array?
Instead of working top-down with recursion, we can build the solution bottom-up. We define dp[j] as a boolean: "can we form sum j using some subset of the elements we have processed so far?"
We start with dp[0] = true (we can always form sum 0 by taking nothing). Then for each number in the array, we update the DP array. If we could previously form sum j, then by including the current number we can now also form sum j + nums[i].
The key trick is iterating from right to left (from target down to nums[i]) when updating. If we iterated left to right, we might use the same element twice in a single subset. By going right to left, when we set dp[j] = true based on dp[j - nums[i]], we know that dp[j - nums[i]] reflects the state before the current element was considered.
The 1D DP array is really a compressed version of the 2D table from Approach 2. In the 2D version, dp[i][j] tells us whether sum j is reachable using elements from index i onward. But each row only depends on the row below it. By processing elements one at a time and iterating right to left, we collapse the 2D table into a single row that gets updated in place. The right-to-left iteration ensures that when we check dp[j - num], it reflects the state before the current element was added, preventing double-counting.
target = totalSum / 2.dp of size target + 1, initialized to false. Set dp[0] = true.num in the array:j from target down to num:dp[j - num] is true, set dp[j] = true.dp[target].