Want Help Cracking FAANG?

(Then click this)

×
Back to Question Bank

1755. Closest Subsequence Sum - Leetcode Solution

Problem Description

The Closest Subsequence Sum problem asks you to find a subsequence of a given integer array nums whose sum is as close as possible to a given goal value. A subsequence is any sequence that can be derived from the array by deleting some or no elements without changing the order of the remaining elements. You must return the absolute difference between the sum of the chosen subsequence and the goal.

  • You can choose any subset of elements (including the empty set and the full array).
  • Each element can be used at most once (no reuse).
  • There is always at least one valid solution.
  • The aim is to minimize abs(subsequence_sum - goal).

Thought Process

At first glance, the problem seems to suggest checking all possible subsequences, calculating their sums, and finding which sum is closest to the goal. However, since the number of subsequences is 2^n for an array of length n, this approach quickly becomes infeasible for larger arrays.

To optimize, we look for ways to reduce the search space. One common trick is to split the array into two halves and work with their subset sums separately, known as the meet-in-the-middle technique. This reduces the complexity from 2^n to 2^{n/2}, which is much more manageable for n up to 40.

The key idea is to precompute all possible subset sums for both halves, then combine them efficiently to find the sum closest to the goal.

Solution Approach

  • Step 1: Divide the Array
    Split nums into two halves: left and right.
  • Step 2: Generate Subset Sums
    For each half, generate all possible subset sums. For an array of length k, there are 2^k possible subset sums, including the sum 0 (the empty subset).
  • Step 3: Sort One List
    Sort the list of subset sums for one of the halves (typically right). This enables efficient binary search.
  • Step 4: Combine and Search
    For each sum in the left subset sums, calculate the complement needed to reach the goal. Use binary search on the sorted right subset sums to find the sum that, when added to the current left sum, gets as close as possible to the goal.
  • Step 5: Track the Closest Sum
    Keep track of the minimum absolute difference found during the process.

This approach leverages efficient subset generation and binary search to drastically reduce the total computation required.

Example Walkthrough

Sample Input:
nums = [5, -7, 3, 5], goal = 6

  1. Divide: left = [5, -7], right = [3, 5]
  2. Subset Sums:
    • left subset sums: 0 (empty), 5, -7, (5 + -7) = -2 → [0, 5, -7, -2]
    • right subset sums: 0, 3, 5, (3 + 5) = 8 → [0, 3, 5, 8]
  3. Sort right sums: [0, 3, 5, 8]
  4. For each left sum, binary search in right for closest to goal - left_sum:
    • left_sum = 0: want closest to 6. right_sum = 5 or 8. 0 + 5 = 5 (diff 1), 0 + 8 = 8 (diff 2).
    • left_sum = 5: want closest to 1. right_sum = 0 (5 + 0 = 5, diff 1), right_sum = 3 (5 + 3 = 8, diff 2).
    • left_sum = -7: want closest to 13. right_sum = 8 (-7 + 8 = 1, diff 5).
    • left_sum = -2: want closest to 8. right_sum = 8 (-2 + 8 = 6, diff 0) → Exact match
  5. Result: Closest difference is 0 (subsequence sum equals goal).

Time and Space Complexity

  • Brute-force: O(2^n) time and space, since every possible subset needs to be checked.
  • Optimized (Meet-in-the-Middle):
    • For each half of size n/2, generating all subset sums takes O(2^{n/2}) time and space.
    • Sorting one list: O(2^{n/2} \log 2^{n/2}) = O(n 2^{n/2}).
    • For each sum in one half (2^{n/2}), binary search in the other half (\log 2^{n/2} = n/2).
    • Total: O(2^{n/2} \cdot n) time and O(2^{n/2}) space.

    This is feasible for n \leq 40.

Summary

The Closest Subsequence Sum problem is a classic example where brute-force is impractical, but the "meet-in-the-middle" technique enables an efficient solution. By splitting the array, precomputing all subset sums for each half, and using binary search to combine them, we can quickly find the closest possible sum to the goal. This approach elegantly balances time and space efficiency, making it suitable for moderately sized arrays.

Code Implementation

from bisect import bisect_left

def closestSum(nums, goal):
    def get_sums(arr):
        n = len(arr)
        sums = []
        for i in range(1 << n):
            total = 0
            for j in range(n):
                if (i >> j) & 1:
                    total += arr[j]
            sums.append(total)
        return sums

    n = len(nums)
    left = nums[:n//2]
    right = nums[n//2:]

    left_sums = get_sums(left)
    right_sums = get_sums(right)
    right_sums.sort()

    res = float('inf')
    for l in left_sums:
        rem = goal - l
        idx = bisect_left(right_sums, rem)
        # check right_sums[idx]
        if idx < len(right_sums):
            res = min(res, abs(l + right_sums[idx] - goal))
        # check right_sums[idx-1]
        if idx > 0:
            res = min(res, abs(l + right_sums[idx-1] - goal))
    return res
      
#include <vector>
#include <algorithm>
#include <cmath>

using namespace std;

class Solution {
public:
    int minAbsDifference(vector<int>& nums, int goal) {
        int n = nums.size();
        int n1 = n / 2;
        int n2 = n - n1;
        vector<int> left(nums.begin(), nums.begin() + n1);
        vector<int> right(nums.begin() + n1, nums.end());
        vector<int> left_sums, right_sums;

        // Generate all subset sums for left
        for (int i = 0; i < (1 << n1); ++i) {
            int sum = 0;
            for (int j = 0; j < n1; ++j) {
                if ((i >> j) & 1) sum += left[j];
            }
            left_sums.push_back(sum);
        }
        // Generate all subset sums for right
        for (int i = 0; i < (1 << n2); ++i) {
            int sum = 0;
            for (int j = 0; j < n2; ++j) {
                if ((i >> j) & 1) sum += right[j];
            }
            right_sums.push_back(sum);
        }
        sort(right_sums.begin(), right_sums.end());
        int res = abs(goal);
        for (int l : left_sums) {
            int rem = goal - l;
            auto it = lower_bound(right_sums.begin(), right_sums.end(), rem);
            if (it != right_sums.end()) {
                res = min(res, abs(l + *it - goal));
            }
            if (it != right_sums.begin()) {
                --it;
                res = min(res, abs(l + *it - goal));
            }
        }
        return res;
    }
};
      
import java.util.*;

class Solution {
    public int minAbsDifference(int[] nums, int goal) {
        int n = nums.length;
        int n1 = n / 2, n2 = n - n1;
        int[] left = Arrays.copyOfRange(nums, 0, n1);
        int[] right = Arrays.copyOfRange(nums, n1, n);

        ArrayList<Integer> leftSums = new ArrayList<>();
        ArrayList<Integer> rightSums = new ArrayList<>();

        for (int i = 0; i < (1 << n1); i++) {
            int sum = 0;
            for (int j = 0; j < n1; j++) {
                if (((i >> j) & 1) == 1) sum += left[j];
            }
            leftSums.add(sum);
        }

        for (int i = 0; i < (1 << n2); i++) {
            int sum = 0;
            for (int j = 0; j < n2; j++) {
                if (((i >> j) & 1) == 1) sum += right[j];
            }
            rightSums.add(sum);
        }

        Collections.sort(rightSums);

        int res = Math.abs(goal);
        for (int l : leftSums) {
            int rem = goal - l;
            int idx = Collections.binarySearch(rightSums, rem);
            if (idx < 0) idx = -idx - 1;
            if (idx < rightSums.size())
                res = Math.min(res, Math.abs(l + rightSums.get(idx) - goal));
            if (idx > 0)
                res = Math.min(res, Math.abs(l + rightSums.get(idx - 1) - goal));
        }
        return res;
    }
}
      
function minAbsDifference(nums, goal) {
    function getSums(arr) {
        const n = arr.length;
        const sums = [];
        for (let i = 0; i < (1 << n); ++i) {
            let sum = 0;
            for (let j = 0; j < n; ++j) {
                if ((i >> j) & 1) sum += arr[j];
            }
            sums.push(sum);
        }
        return sums;
    }
    const n = nums.length;
    const left = nums.slice(0, Math.floor(n / 2));
    const right = nums.slice(Math.floor(n / 2));
    const leftSums = getSums(left);
    const rightSums = getSums(right);
    rightSums.sort((a, b) => a - b);

    let res = Math.abs(goal);
    for (const l of leftSums) {
        const rem = goal - l;
        let lo = 0, hi = rightSums.length;
        while (lo < hi) {
            const mid = Math.floor((lo + hi) / 2);
            if (rightSums[mid] < rem) lo = mid + 1;
            else hi = mid;
        }
        if (lo < rightSums.length)
            res = Math.min(res, Math.abs(l + rightSums[lo] - goal));
        if (lo > 0)
            res = Math.min(res, Math.abs(l + rightSums[lo - 1] - goal));
    }
    return res;
}