Post

Dynamic Programming - Partition Equal Subset Sum

All diagrams presented herein are original creations, meticulously designed to enhance comprehension and recall. Crafting these aids required considerable effort, and I kindly request attribution if this content is reused elsewhere.

Difficulty : Easy

DP

Problem

Given an integer array nums, return true if you can partition the array into two subsets such that the sum of the elements in both subsets is equal or false otherwise.

Example 1:

1
2
3
Input: nums = [1,5,11,5]
Output: true
Explanation: The array can be partitioned as [1, 5, 5] and [11].

Example 2:

1
2
3
Input: nums = [1,2,3,5]
Output: false
Explanation: The array cannot be partitioned into equal sum subsets.

Solution

Before we get started with any solution, the following is very clear. We need to use the elements in any order and find if they could make two groups each having sum equal to target.

1
2
3
4
if sum(nums)%2==1:
  return False

target = sum(nums)//2

DFS Solution

Seems fairly straightforward from a DFS solution standpoint. We can use template 2 that we have already discussed here.

The idea is not to find two sets of group, rather just find one path which leads to the target. Since we know the actual sum is double than the target then the remaining numbers must be added to make the other target value.

:fire: Now if we restructure the problem to this - “ Find if the elements of an array can make the target value when used only once”, it becomes much simpler to implement and we can use the same intuition when implementing using the dynamic programming approach.

Start with the terminating conditions, if the current target==0 then we know we have successfully reached to the end, so can return True, conversely if the target anytime is negative then we know we have overshoot the target and current path can not reach to a valid target, so we return False.

1
2
3
4
5
def dfs(index, target):
  if target == 0:
    return True
  if target < 0:
    return False

Now if we still have a positive target, we can loop and consider every other elements to find the target. If we ever find a True immediately return it. In case there is no success, return False at the end of the loop.

1
2
3
4
5
  for j in range(index,len(nums)):
    if dfs(j+1, target - nums[j]):
      return True
  return False
      

Finally call dfs() and return its return.

1
return dfs(0,target)

Even though this is a valid code, it will take O(2^n) time, hence won’t pass LeetCode, however we will use the similar concept below to implement using the dynamic programming.

Dynamic Programming

As discussed earlier we will use the same intuition. We need to find a target sum by using the numbers only once from an array. One way we can do this in O(n) time is for every element we find the sum for every combination. The space complexity will be large though.

Below is an example, we are going to iterate through the array from left to right and keep all the summations in a set. Initially we start with a set having just a 0 as base case. Then we add 1 and keep both. Then we take 5, find the new summations and append them to the set as well (0 + 5 = 5, 1 + 5 = 6). We can keep doing this till the end of the loop.

image-20240527161314232

If we have target in the set then we know we can reach it using the numbers to we return True. The best part is, we don’t even have to wait for the loop to complete, we can return True as soon as there is a target in the set.

1
2
3
4
5
6
7
8
9
10
cache = set([0])
for i in range(len(nums)):
  copy_of_cache=cache.copy()
  for digit in copy_of_cache:
    cache.add(nums[i]+digit)
    # We do not have to wait for the loop to complete.
    if target in cache:
      return True

return False

Final Code

Here is the full code.

DFS

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
def can_partition(nums):
    if sum(nums) % 2 == 1:
        return False
    
    target = sum(nums)//2  
    
    def dfs(index, target):
        if target == 0:
            return True
        if target < 0:
            return False
        for j in range(index,len(nums)):
            if dfs(j+1, target - nums[j]):
                return True
        return False   
    return dfs(0, target)

Dynamic Programming

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
def can_partition(nums):
    if sum(nums) % 2 == 1:
        return False

    target = sum(nums)//2
    cache = set([0])
    for i in range(len(nums)):
        copy_of_cache = cache.copy()
        for digit in copy_of_cache:
            cache.add(nums[i]+digit)
            
        # We do not have to wait for the loop to complete.
        if target in cache:
            return True

    return False
This post is licensed under CC BY 4.0 by the author.