1498. Number of Subsequences That Satisfy The Given Sum Condition - Explanation
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: 4Explanation: 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: 6Explanation: 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,0001 <= nums[i], target <= 1,000,000
Topics
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
- Use a recursive function with parameters for current index, running
max, and runningmin. - At each index, branch into two choices: skip the element or include it.
- When including, update the running
minandmax. - At the end of the array, check if we have a valid non-empty subsequence where
min + max <= target. - 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:
- Space complexity: for recursion stack.
2. Binary Search
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
- Sort the array.
- For each index
iwherenums[i] * 2 <= target(it can be both min and max):- Binary search for the largest index
rwherenums[i] + nums[r] <= target. - The elements between
iandrcan each be included or not, giving2^(r-i)subsequences.
- Binary search for the largest index
- 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 resTime & Space Complexity
- Time complexity:
- Space complexity: or 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
- Sort the array and initialize left pointer at
0, right pointer at the last index. - For each left pointer position:
- Shrink the right pointer until
nums[left] + nums[right] <= target. - If valid
(left <= right), add2^(right - left)subsequences. - Move left forward.
- Shrink the right pointer until
- 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 resTime & Space Complexity
- Time complexity:
- Space complexity: or 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
- Sort the array and precompute
power[i] = 2^i mod (10^9 + 7)forifrom0ton-1. - Use two pointers starting at both ends.
- While
left <= right:- If
nums[left] + nums[right] <= target, addpower[right - left]toresand incrementleft. - Otherwise, decrement
right.
- If
- 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 resTime & Space Complexity
- Time complexity:
- Space complexity:
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.
Sign in to join the discussion