Skip to content

LeetCode 18: 4Sum

Problem Overview ๐Ÿค”

The 4Sum problem is a classic array manipulation challenge that builds upon the concepts of Two Sum and Three Sum. Let's break down what we're trying to solve!

Given an array nums of n integers and a target value, we need to find all unique quadruplets that sum up to the target. It's like finding four puzzle pieces that fit perfectly together! ๐Ÿงฉ

Problem Constraints

1 <= nums.length <= 200
-109 <= nums[i] <= 109
-109 <= target <= 109

The Journey to an Optimal Solution ๐Ÿš€

Let's explore different approaches to solve this problem, starting from a basic solution and gradually optimizing it. Each solution brings something unique to the table!

Solution 1: Recursive Approach with Early Pruning ๐ŸŒณ

Our first solution takes a recursive approach, breaking down the 4Sum problem into smaller subproblems. Think of it like a tree where each level represents one number in our quadruplet.

Key Insights:

  • ๐Ÿ” Sort the array first to handle duplicates and enable pruning
  • ๐Ÿšซ Early pruning using sum checks
  • ๐Ÿ”„ Recursive reduction from 4Sum to 3Sum to 2Sum
from typing import List


class Solution:
    def fourSum(self, nums: List[int], target: int) -> List[List[int]]:
        nums.sort()
        results = []
        if len(nums)<4:
            return results
        if sum(nums[:4])> target:
            return results
        if sum(nums[-4:])< target:
            return results
        for i, num in enumerate(nums[:-3]):
            current_result = self.three_sum(nums[i+1:], target-nums[i])
            current_result = [xi + [num] for xi in current_result]
            results += current_result
        results = [tuple(x) for x in results]
        results = list(set(results))
        results = [list(xi) for xi in results]
        return results

    def three_sum(self, nums, target):
        results = []
        if sum(nums[:3])> target:
            return results
        if sum(nums[-3:])< target:
            return results
        for i, num in enumerate(nums[:-2]):
            current_results = self.two_sum(nums[i+1:], target-nums[i])
            current_results = [xi + [num] for xi in current_results]
            results += current_results
        return results



    def two_sum(self, nums, target):
        # assume nums is sorted
        n = len(nums)
        i = 0
        j = n-1
        current_sum = nums[i]+nums[j]
        results = []
        if sum(nums[:2]) > target:
            return results
        if sum(nums[-2:]) < target:
            return results
        while i<j:
            current_sum = nums[i]+nums[j]

            if current_sum==target:
                results.append([nums[i], nums[j]])
                i +=1
            elif current_sum < target:
                i += 1
            else:
                j -= 1
        return results




if __name__ == '__main__':
    nums = [1,0,-1,0,-2,2]
    target = 0
    s = Solution()
    y = s.fourSum(nums=nums,target=target)
    print(y)

This solution works by:

  1. ๐Ÿ“Š Sorting the array first
  2. โœ‚๏ธ Pruning impossible cases early (if smallest 4 sum > target or largest 4 sum < target)
  3. ๐Ÿ” Using recursion to break down the problem
  4. ๐ŸŽฏ Handling duplicates with set conversion

Solution 2: Optimizing Two Sum with Early Return ๐Ÿƒโ€โ™‚๏ธ

In our second solution, we made a key optimization in the two_sum function. By adding an early return when we find a match, we can speed up the process significantly!

What Changed:

  • โšก Added j -= 1 for faster traversal
  • ๐ŸŽฏ Early return on match
  • ๐Ÿ”„ Maintained the same overall structure
    def two_sum(self, nums, target):
        # assume nums is sorted
        n = len(nums)
        i = 0
        j = n-1
        current_sum = nums[i]+nums[j]
        results = []
        if sum(nums[:2]) > target:
            return results
        if sum(nums[-2:]) < target:
            return results
        while i<j:
            current_sum = nums[i]+nums[j]

            if current_sum==target:
                results.append([nums[i], nums[j]])
                i +=1
                j -=1 # Since we dont need to care the duplicatesd
            elif current_sum < target:
                i += 1
            else:
                j -= 1
        return results

The results speak for themselves: alt text

Solution 3: Smart Duplicate Handling ๐Ÿง 

Our third solution introduces smart duplicate handling, which significantly improves performance by skipping redundant calculations.

Key Improvements:

  • ๐Ÿƒโ€โ™‚๏ธ Skip duplicate values
  • ๐ŸŽฏ More efficient pointer movement
  • ๐Ÿ’ก Smarter comparison logic
    def two_sum(self, nums, target):
        # assume nums is sorted
        n = len(nums)
        i = 0
        j = n-1
        results = []
        if sum(nums[:2]) > target:
            return results
        if sum(nums[-2:]) < target:
            return results
        while i<j:
            current_sum = nums[i]+nums[j]
            if current_sum==target:
                results.append([nums[i], nums[j]])
                i +=1
                while i < j and nums[i] == nums[i-1]:
                    i += 1
                j -=1
                while i<j and nums[j]==nums[j+1]:
                    j -= 1
            elif current_sum < target:
                i += 1

            else:
                j -= 1

Performance improvement shown here: alt text

Solution 4: The Ultimate Optimization ๐Ÿ†

Finally, we reached our most optimized solution using recursion with smart pruning and efficient duplicate handling.

Advanced Features:

  • ๐Ÿš€ Early termination conditions
  • ๐ŸŽฏ Smart pointer management
  • ๐Ÿ”„ Efficient recursive approach
  • ๐Ÿงฎ Optimized memory usage
class Solution5:
    def fourSum(self, nums, target):
        def findNsum(l, r, target, N, result, results):
            if r-l+1 < N or N < 2 or target < nums[l]*N or target > nums[r]*N:  # early termination
                return
            if N == 2: # two pointers solve sorted 2-sum problem
                results += [result+x for x in self.two_sum(l,r,nums, target)]
            else: # recursively reduce N
                for i in range(l, r+1):
                    if i == l or (i > l and nums[i-1] != nums[i]):
                        findNsum(i+1, r, target-nums[i], N-1, result+[nums[i]], results)

        nums.sort()
        results = []
        findNsum(0, len(nums)-1, target, 4, [], results)
        return results

    def two_sum(self,l,r, nums, target):
        results =[]
        while l < r:
            s = nums[l] + nums[r]
            if s == target:
                results.append([nums[l], nums[r]])
                l += 1
                while l < r and nums[l] == nums[l-1]:
                    l += 1
                r -=1
                while l<r and nums[r] ==nums[r+1]:
                    r -= 1
            elif s < target:
                l += 1
            else:
                r -= 1
        return results

Final performance results: alt text

Time Complexity Analysis ๐Ÿ“Š

Let's break down the complexity of our solutions:

  • Base Solution: O(nยณ) - Three nested loops with binary search
  • Optimized Solution: Still O(nยณ) but with much better constant factors due to:
  • Early pruning ๐Ÿšซ
  • Duplicate skipping โญ๏ธ
  • Smart pointer management ๐Ÿ‘†

Key Takeaways ๐Ÿ’ก

  1. Early Pruning is crucial for performance
  2. Smart Duplicate Handling can significantly reduce unnecessary calculations
  3. Pointer Management is key in array problems
  4. Space-Time Tradeoffs are important considerations

Practice Tips ๐Ÿ“

  1. ๐ŸŽฏ Start with the basic solution and understand it thoroughly
  2. ๐Ÿ”„ Practice with different input sizes
  3. ๐Ÿงช Test edge cases carefully
  4. ๐Ÿ“Š Compare performance with different approaches

Want to master array manipulation and sum problems? Here are some related LeetCode problems to practice:

Two Sum Family ๐Ÿ‘ฅ

  1. Two Sum (Easy)
  2. The classic problem that started it all
  3. Find two numbers that add up to target
  4. Great starting point for understanding the basics

  5. Two Sum II - Input Array Is Sorted (Medium)

  6. Similar to Two Sum but with sorted input
  7. Uses two-pointer technique
  8. Good practice for optimizing with sorted arrays

Three Sum Family ๐Ÿ‘จโ€๐Ÿ‘ฆโ€๐Ÿ‘ฆ

  1. 3Sum (Medium)
  2. Find triplets that sum to zero
  3. Natural progression from Two Sum
  4. Introduces duplicate handling

  5. 3Sum Closest (Medium)

  6. Find triplet with sum closest to target
  7. Similar approach but different goal
  8. Good for understanding approximation problems

Four Sum and Beyond ๐Ÿ‘จโ€๐Ÿ‘ฉโ€๐Ÿ‘งโ€๐Ÿ‘ฆ

  1. 4Sum II (Medium)
  2. Four arrays instead of one
  3. Uses hash map approach
  4. Different take on the 4Sum problem

  5. K Sum (Hard)

  6. Generalized version for K numbers
  7. Tests understanding of recursive solutions
  8. Advanced problem combining multiple concepts

Similar Pattern Problems ๐ŸŽฏ

  1. Combination Sum (Medium)
  2. Find combinations that sum to target
  3. Uses backtracking
  4. Different approach to sum problems

  5. Subarray Sum Equals K (Medium)

  6. Continuous subarray summing to target
  7. Uses prefix sum technique
  8. Different perspective on sum problems

Problem-Solving Tips for Sum Problems ๐Ÿ’ก

  1. Always consider sorting the array first if order doesn't matter
  2. Two-pointer technique is powerful for sorted arrays
  3. Hash maps can often optimize brute force solutions
  4. Watch out for duplicates and overflow cases
  5. Consider space-time tradeoffs in your solutions

Keep practicing these problems to build a strong foundation in array manipulation and algorithmic thinking! ๐Ÿ’ช