1498. Number of Subsequences That Satisfy The Given Sum Condition - Explanation

Problem Link

Description

You are given an array of integers nums and an integer target.

Return the number of non-empty subsequences of nums such that the sum of the minimum and maximum element on it is less or equal to target. Since the answer may be too large, return it modulo (10^9) + 7.

Example 1:

Input: nums = [3,5,6,7], target = 9

Output: 4

Explanation: There are 4 subsequences that satisfy the condition.
[3] -> Min value + max value <= target (3 + 3 <= 9)
[3,5] -> (3 + 5 <= 9)
[3,5,6] -> (3 + 6 <= 9)
[3,6] -> (3 + 6 <= 9)

Example 2:

Input: nums = [3,3,6,8], target = 10

Output: 6

Explanation: There are 6 subsequences that satisfy the condition. (nums can have repeated numbers).
[3] , [3] , [3,3], [3,6] , [3,6] , [3,3,6]

Constraints:

  • 1 <= nums.length <= 100,000
  • 1 <= nums[i], target <= 1,000,000


Topics

Company Tags

Please upgrade to NeetCode Pro to view company tags.



Prerequisites

Before attempting this problem, you should be comfortable with:

  • Sorting - Sorting allows us to efficiently find valid ranges where min + max satisfies the condition
  • Two Pointers - Used to find valid subsequence ranges from both ends of the sorted array
  • Binary Search - Alternative approach to find the rightmost valid index for each starting element
  • Modular Arithmetic - Required for computing powers of 2 modulo 10^9+7 to count subsequences

1. Brute Force (Recursion)

Intuition

We generate all possible subsequences using recursion, tracking the minimum and maximum values chosen so far. If a non-empty subsequence has min + max <= target, we count it. This explores the full power set of the array.

Algorithm

  1. Use a recursive function with parameters for current index, running max, and running min.
  2. At each index, branch into two choices: skip the element or include it.
  3. When including, update the running min and max.
  4. At the end of the array, check if we have a valid non-empty subsequence where min + max <= target.
  5. Return the total count modulo 10^9 + 7.
class Solution:
    def numSubseq(self, nums: List[int], target: int) -> int:
        MOD = 1000000007

        def dfs(maxi, mini, i):
            if i == len(nums):
                if mini != float("inf") and (maxi + mini) <= target:
                    return 1
                return 0

            skip = dfs(maxi, mini, i + 1)
            include = dfs(max(maxi, nums[i]), min(mini, nums[i]), i + 1)
            return (skip + include) % MOD

        return dfs(float("-inf"), float("inf"), 0)

Time & Space Complexity

  • Time complexity: O(2n)O(2 ^ n)
  • Space complexity: O(n)O(n) for recursion stack.

Intuition

After sorting, for each element as the minimum, we use binary search to find the rightmost element that can serve as the maximum (where min + max <= target). All elements between these two positions can freely be included or excluded, giving 2^(count) valid subsequences with this minimum.

Algorithm

  1. Sort the array.
  2. For each index i where nums[i] * 2 <= target (it can be both min and max):
    • Binary search for the largest index r where nums[i] + nums[r] <= target.
    • The elements between i and r can each be included or not, giving 2^(r-i) subsequences.
  3. Sum all counts modulo 10^9 + 7.
class Solution:
    def numSubseq(self, nums: List[int], target: int) -> int:
        nums.sort()
        MOD = 1000000007
        res = 0

        for i in range(len(nums)):
            if nums[i] * 2 > target:
                break

            l, r = i, len(nums) - 1
            while l <= r:
                mid = (l + r) // 2
                if nums[i] + nums[mid] <= target:
                    l = mid + 1
                else:
                    r = mid - 1

            count = pow(2, r - i, MOD)
            res = (res + count) % MOD

        return res

Time & Space Complexity

  • Time complexity: O(nlogn)O(n \log n)
  • Space complexity: O(1)O(1) or O(n)O(n) depending on the sorting algorithm.

3. Two Pointers

Intuition

After sorting, we use two pointers. The left pointer represents the minimum element we must include. The right pointer starts at the end and shrinks inward until the sum constraint is satisfied. Since the array is sorted, once the right pointer moves left, it never needs to go back.

Algorithm

  1. Sort the array and initialize left pointer at 0, right pointer at the last index.
  2. For each left pointer position:
    • Shrink the right pointer until nums[left] + nums[right] <= target.
    • If valid (left <= right), add 2^(right - left) subsequences.
    • Move left forward.
  3. Return the total count modulo 10^9 + 7.
class Solution:
    def numSubseq(self, nums: List[int], target: int) -> int:
        nums.sort()
        res = 0
        mod = 10**9 + 7

        r = len(nums) - 1
        for i, left in enumerate(nums):
            while i <= r and left + nums[r] > target:
                r -= 1
            if i <= r:
                res += pow(2, r - i, mod)
                res %= mod

        return res

Time & Space Complexity

  • Time complexity: O(nlogn)O(n \log n)
  • Space complexity: O(1)O(1) or O(n)O(n) depending on the sorting algorithm.

4. Two Pointers (Optimal)

Intuition

We precompute all powers of 2 up to n to avoid repeated exponentiation. The two pointer logic moves inward from both ends: if the current pair satisfies the constraint, count the subsequences and advance the left pointer; otherwise, shrink the right pointer.

Algorithm

  1. Sort the array and precompute power[i] = 2^i mod (10^9 + 7) for i from 0 to n-1.
  2. Use two pointers starting at both ends.
  3. While left <= right:
    • If nums[left] + nums[right] <= target, add power[right - left] to res and increment left.
    • Otherwise, decrement right.
  4. Return the total count.
class Solution:
    def numSubseq(self, nums: List[int], target: int) -> int:
        nums.sort()
        MOD = 1000000007
        res = 0
        l, r = 0, len(nums) - 1
        power = [1] * len(nums)

        for i in range(1, len(nums)):
            power[i] = (power[i - 1] * 2) % MOD

        while l <= r:
            if nums[l] + nums[r] <= target:
                res = (res + power[r - l]) % MOD
                l += 1
            else:
                r -= 1

        return res

Time & Space Complexity

  • Time complexity: O(nlogn)O(n \log n)
  • Space complexity: O(n)O(n)

Common Pitfalls

Forgetting to Sort the Array

The two-pointer and binary search approaches require a sorted array. Since we only care about the minimum and maximum values in each subsequence (not their order), sorting is valid and necessary. Forgetting to sort leads to incorrect results because the two-pointer logic assumes sorted order.

Integer Overflow in Power Calculation

Computing 2^(r-l) can produce astronomically large numbers. Using naive exponentiation without modular arithmetic causes overflow. You must use modular exponentiation or precompute powers with modulo applied at each step to avoid this issue.

Misunderstanding the Counting Formula

For a valid range from index l to r where nums[l] is the minimum, there are 2^(r-l) valid subsequences, not 2^(r-l+1). This is because we must include nums[l] (it's the minimum), but any subset of the remaining r-l elements can be included. Off-by-one errors in this exponent are common.