1498. Number of Subsequences That Satisfy the Given Sum Condition
Problem Description
You have an array of integers nums and an integer target.
Your task is to count how many non-empty subsequences of nums satisfy this condition: when you take the minimum and maximum elements from the subsequence and add them together, their sum must be less than or equal to target.
A subsequence means you can pick any elements from the array while keeping their original order, but you don't have to pick consecutive elements. For example, if nums = [3, 5, 6], then [3], [5], [6], [3, 5], [3, 6], [5, 6], and [3, 5, 6] are all valid subsequences.
For each subsequence you form, you need to:
- Find the minimum element in that subsequence
- Find the maximum element in that subsequence
- Check if
minimum + maximum ≤ target
Count all subsequences that meet this condition and return the count modulo 10^9 + 7 (since the answer could be very large).
For example, if nums = [3, 5, 6, 7] and target = 9:
- Subsequence
[3, 5]has min=3, max=5, sum=8 ≤ 9 ✓ - Subsequence
[3, 5, 6]has min=3, max=6, sum=9 ≤ 9 ✓ - Subsequence
[3, 6, 7]has min=3, max=7, sum=10 > 9 ✗ - And so on...
The solution leverages the fact that after sorting, for each minimum element, we can use binary search to find the rightmost valid maximum element, and all elements between them can be either included or excluded in the subsequence, giving us 2^(j-i) valid subsequences for that particular minimum.
Intuition
The key insight is that in any subsequence, only the minimum and maximum elements matter for our condition. This means we don't care about the values of elements between them - we only need to count how many different ways we can select them.
Let's think about this step by step:
-
Why sorting helps: If we sort the array, we can easily identify which element would be the minimum and which would be the maximum in any subsequence. In a sorted array, for any subsequence containing elements from index
ito indexj, we knownums[i]is the minimum andnums[j]is the maximum. -
Fixing the minimum: Once we fix
nums[i]as the minimum element of our subsequence, we need to find all valid maximum elements. Since the array is sorted, any elementnums[j]wherej > icould potentially be the maximum, as long asnums[i] + nums[j] ≤ target. -
Finding the boundary: For a fixed minimum
nums[i], there's a rightmost indexjwherenums[i] + nums[j] ≤ target. Any element beyond this index would violate our condition. We can find this boundary efficiently using binary search. -
Counting subsequences: Here's the clever part - once we have a valid range from
itoj, how many subsequences are there? We must includenums[i](the minimum) andnums[j](the maximum), but for every element between them (indicesi+1toj-1), we have a choice: include it or not. This gives us2^(j-i)different subsequences for this particular minimum and maximum pair. -
Early termination: If
nums[i] * 2 > target, it means even the smallest possible sum (when min = max =nums[i]) exceeds the target, so we can stop our search early.
The pre-computed powers of 2 in array f help us avoid recalculating 2^k repeatedly, making the solution more efficient.
Learn more about Two Pointers, Binary Search and Sorting patterns.
Solution Implementation
1class Solution:
2 def numSubseq(self, nums: List[int], target: int) -> int:
3 MOD = 10**9 + 7
4
5 # Sort the array to easily identify min and max in subsequences
6 nums.sort()
7 n = len(nums)
8
9 # Precompute powers of 2 for efficiency
10 power_of_two = [1] * n
11 for i in range(1, n):
12 power_of_two[i] = (power_of_two[i - 1] * 2) % MOD
13
14 result = 0
15
16 # Iterate through each element as the minimum of a subsequence
17 for left_idx, min_val in enumerate(nums):
18 # If minimum element doubled exceeds target, no valid subsequences exist
19 if min_val * 2 > target:
20 break
21
22 # Binary search for rightmost index where nums[right_idx] + min_val <= target
23 # feasible(mid) = nums[mid] <= target - min_val
24 # We want the LAST true index, so we search for first where nums[mid] > max_allowed
25 max_allowed = target - min_val
26 left = left_idx
27 right = n - 1
28 first_true_index = -1
29
30 while left <= right:
31 mid = (left + right) // 2
32 if nums[mid] > max_allowed: # feasible: found an element too large
33 first_true_index = mid
34 right = mid - 1
35 else:
36 left = mid + 1
37
38 # right_idx is the last valid index (one before first_true_index)
39 right_idx = first_true_index - 1 if first_true_index != -1 else n - 1
40
41 # Count subsequences with min_val as minimum
42 subsequence_count = power_of_two[right_idx - left_idx]
43 result = (result + subsequence_count) % MOD
44
45 return result
461class Solution {
2 public int numSubseq(int[] nums, int target) {
3 Arrays.sort(nums);
4
5 final int MOD = 1_000_000_007;
6 int n = nums.length;
7
8 // Precompute powers of 2
9 int[] powerOfTwo = new int[n + 1];
10 powerOfTwo[0] = 1;
11 for (int i = 1; i <= n; i++) {
12 powerOfTwo[i] = (powerOfTwo[i - 1] * 2) % MOD;
13 }
14
15 int result = 0;
16
17 // For each element as the minimum of a subsequence
18 for (int minIndex = 0; minIndex < n && nums[minIndex] * 2 <= target; minIndex++) {
19 int maxAllowed = target - nums[minIndex];
20
21 // Binary search for first index where nums[mid] > maxAllowed
22 int left = minIndex;
23 int right = n - 1;
24 int firstTrueIndex = -1;
25
26 while (left <= right) {
27 int mid = left + (right - left) / 2;
28 if (nums[mid] > maxAllowed) {
29 firstTrueIndex = mid;
30 right = mid - 1;
31 } else {
32 left = mid + 1;
33 }
34 }
35
36 // maxIndex is the last valid index (one before firstTrueIndex)
37 int maxIndex = (firstTrueIndex != -1) ? firstTrueIndex - 1 : n - 1;
38
39 result = (result + powerOfTwo[maxIndex - minIndex]) % MOD;
40 }
41
42 return result;
43 }
44}
451class Solution {
2public:
3 int numSubseq(vector<int>& nums, int target) {
4 sort(nums.begin(), nums.end());
5
6 const int MOD = 1e9 + 7;
7 int n = nums.size();
8
9 // Precompute powers of 2
10 vector<int> power(n + 1);
11 power[0] = 1;
12 for (int i = 1; i <= n; ++i) {
13 power[i] = (power[i - 1] * 2) % MOD;
14 }
15
16 int result = 0;
17
18 for (int i = 0; i < n && nums[i] * 2 <= target; ++i) {
19 int maxAllowed = target - nums[i];
20
21 // Binary search for first index where nums[mid] > maxAllowed
22 int left = i;
23 int right = n - 1;
24 int firstTrueIndex = -1;
25
26 while (left <= right) {
27 int mid = left + (right - left) / 2;
28 if (nums[mid] > maxAllowed) {
29 firstTrueIndex = mid;
30 right = mid - 1;
31 } else {
32 left = mid + 1;
33 }
34 }
35
36 // j is the last valid index (one before firstTrueIndex)
37 int j = (firstTrueIndex != -1) ? firstTrueIndex - 1 : n - 1;
38
39 result = (result + power[j - i]) % MOD;
40 }
41
42 return result;
43 }
44};
451function numSubseq(nums: number[], target: number): number {
2 nums.sort((a, b) => a - b);
3
4 const MOD = 1e9 + 7;
5 const n = nums.length;
6
7 // Precompute powers of 2
8 const powers: number[] = Array(n + 1).fill(1);
9 for (let i = 1; i <= n; i++) {
10 powers[i] = (powers[i - 1] * 2) % MOD;
11 }
12
13 let result = 0;
14
15 for (let minIndex = 0; minIndex < n && nums[minIndex] * 2 <= target; minIndex++) {
16 const maxAllowed = target - nums[minIndex];
17
18 // Binary search for first index where nums[mid] > maxAllowed
19 let left = minIndex;
20 let right = n - 1;
21 let firstTrueIndex = -1;
22
23 while (left <= right) {
24 const mid = Math.floor((left + right) / 2);
25 if (nums[mid] > maxAllowed) {
26 firstTrueIndex = mid;
27 right = mid - 1;
28 } else {
29 left = mid + 1;
30 }
31 }
32
33 // maxIndex is the last valid index (one before firstTrueIndex)
34 const maxIndex = firstTrueIndex !== -1 ? firstTrueIndex - 1 : n - 1;
35
36 result = (result + powers[maxIndex - minIndex]) % MOD;
37 }
38
39 return result;
40}
41Solution Approach
The implementation follows the sorting and binary search strategy outlined in the reference approach:
Step 1: Preprocessing
- Sort the array
numsin ascending order - Pre-compute powers of 2 up to
nand store them in arrayf, wheref[i] = 2^i % mod- This avoids redundant power calculations during the main loop
- Each
f[i] = f[i-1] * 2 % mod
Step 2: Main Algorithm
For each index i in the sorted array:
-
Set minimum element: Consider
nums[i]as the minimum element of the subsequence -
Early termination check: If
nums[i] * 2 > target, break the loop- This means even a subsequence with just
nums[i](where min = max) exceeds the target - Since the array is sorted, all subsequent elements will also fail this check
- This means even a subsequence with just
-
Find maximum valid index using template-based binary search:
- Define the feasible condition:
nums[mid] > maxAllowedwheremaxAllowed = target - nums[i] - This creates a pattern:
false, false, ..., true, true, truein the sorted array - Use the standard template with
firstTrueIndex = -1,while left <= right, andright = mid - 1 - The rightmost valid index
jisfirstTrueIndex - 1(orn - 1if no element exceedsmaxAllowed)
- Define the feasible condition:
-
Count valid subsequences: For the range
[i, j], add2^(j-i)to the answer- We must include
nums[i](minimum) andnums[j](maximum) - For the
j - i - 1elements between them, each can be either included or excluded - This gives us
2^(j-i)total subsequences - Access the pre-computed value using
f[j - i]
- We must include
-
Apply modulo: Keep the answer within bounds using
mod = 10^9 + 7
Time Complexity: O(n log n) for sorting + O(n log n) for n binary searches = O(n log n)
Space Complexity: O(n) for storing the powers of 2
Ready to land your dream job?
Unlock your dream job with a 5-minute evaluator for a personalized learning plan!
Start EvaluatorExample Walkthrough
Let's walk through a concrete example with nums = [3, 5, 6, 7] and target = 9.
Step 1: Sort and Precompute Powers of 2
- Array is already sorted:
[3, 5, 6, 7] - Precompute powers of 2:
f = [1, 2, 4, 8, 16]wheref[k] = 2^k
Step 2: Process Each Element as Minimum
Iteration 1: i = 0, minimum = 3
- Check early termination:
3 * 2 = 6 ≤ 9, so continue - Find maximum valid index using binary search:
- Looking for largest
jwherenums[j] ≤ 9 - 3 = 6 - Binary search finds
j = 2(nums[2] = 6)
- Looking for largest
- Count subsequences from index 0 to 2:
- We have elements
[3, 5, 6]where 3 is min, 6 is max - Number of subsequences =
2^(2-0) = 2^2 = 4 - These subsequences are:
[3, 6]- must include min and max[3, 5, 6]- include the middle element- Actually, let me recalculate: for range [0,2], we fix nums[0]=3 as min and nums[2]=6 as max
- Between them we have 1 element (nums[1]=5) which can be included or excluded
- So we have
2^1 = 2subsequences:[3, 6]and[3, 5, 6]
- We have elements
- Wait, I need to correct this. The formula is
2^(j-i-1)for elements between, but the code usesf[j-i]. Let me reconsider. - Actually, looking at the code, it adds
f[j - i]which is2^(j-i). For j=2, i=0:f[2] = 4 - This counts all subsequences where 3 is minimum and any element up to index 2 can be maximum:
[3](min=max=3, sum=6)[3, 5](min=3, max=5, sum=8)[3, 6](min=3, max=6, sum=9)[3, 5, 6](min=3, max=6, sum=9)
- Total so far: 4
Iteration 2: i = 1, minimum = 5
- Check early termination:
5 * 2 = 10 > 9, so break - Since even the smallest possible sum (5+5=10) exceeds target, we stop
Final Answer: 4
The valid subsequences are:
[3]with min=3, max=3, sum=6 ≤ 9 ✓[3, 5]with min=3, max=5, sum=8 ≤ 9 ✓[3, 6]with min=3, max=6, sum=9 ≤ 9 ✓[3, 5, 6]with min=3, max=6, sum=9 ≤ 9 ✓
Note how the algorithm efficiently counts these without explicitly generating each subsequence. By fixing the minimum element and finding the valid range for the maximum, it uses the power of 2 formula to count all possibilities at once.
Time and Space Complexity
Time Complexity: O(n × log n)
The time complexity breaks down as follows:
- Sorting the array takes
O(n × log n)time - Pre-computing powers of 2 in array
ftakesO(n)time - The main loop iterates through each element once, taking
O(n)iterations - Inside each iteration,
bisect_rightperforms binary search, which takesO(log n)time - Overall:
O(n × log n) + O(n) + O(n × log n) = O(n × log n)
Space Complexity: O(n)
The space complexity consists of:
- Array
fstoresn + 1precomputed powers of 2, requiringO(n)space - The sorting operation may use
O(log n)toO(n)auxiliary space depending on the implementation (Python's Timsort uses up toO(n)in worst case) - Other variables (
mod,n,ans,i,x,j) useO(1)space - Overall:
O(n)space is required
Learn more about how to find time and space complexity quickly.
Common Pitfalls
1. Using Wrong Binary Search Template Variant
A common mistake is using while left < right with right = mid instead of the standard template. This variant is harder to reason about and can lead to off-by-one errors.
Incorrect Implementation:
# Wrong template variant - harder to reason about left, right = 0, n while left < right: mid = (left + right) // 2 if nums[mid] > max_allowed: right = mid else: left = mid + 1 return left
Correct Implementation:
# Standard template - tracks first true index explicitly left, right = 0, n - 1 first_true_index = -1 while left <= right: mid = (left + right) // 2 if nums[mid] > max_allowed: first_true_index = mid right = mid - 1 else: left = mid + 1 # Use first_true_index - 1 for rightmost valid index
2. Incorrect Power of 2 Calculation Without Modulo
Computing powers of 2 without applying modulo during the precomputation phase leads to integer overflow for large values of n.
Incorrect Implementation:
# This will cause overflow for large n
power_of_two = [1] * n
for i in range(1, n):
power_of_two[i] = power_of_two[i - 1] * 2 # Missing modulo!
Correct Implementation:
power_of_two = [1] * n
for i in range(1, n):
power_of_two[i] = (power_of_two[i - 1] * 2) % MOD
3. Not Handling first_true_index == -1 Edge Case
When all elements satisfy the condition (none exceed maxAllowed), first_true_index remains -1. Failing to handle this case leads to incorrect index calculation.
Incorrect Implementation:
# Crashes or gives wrong result when first_true_index is -1 right_idx = first_true_index - 1
Correct Implementation:
# Handle the case where no element exceeds maxAllowed right_idx = first_true_index - 1 if first_true_index != -1 else n - 1
4. Forgetting to Sort the Array
The algorithm relies on the sorted property to correctly identify min/max elements and perform binary search. Without sorting, the logic completely breaks down.
Incorrect Implementation:
def numSubseq(self, nums: List[int], target: int) -> int:
# Missing nums.sort()!
n = len(nums)
# Rest of the code...
Correct Implementation:
def numSubseq(self, nums: List[int], target: int) -> int:
nums.sort() # Essential for the algorithm to work
n = len(nums)
# Rest of the code...
5. Incorrect Subsequence Count Formula
Using 2^(j-i-1) instead of 2^(j-i) undercounts valid subsequences by not accounting for all possible combinations of intermediate elements.
Incorrect Implementation:
# This formula is wrong - it doesn't count all valid subsequences subsequence_count = power_of_two[right_idx - left_idx - 1]
Correct Implementation:
# Correct formula: 2^(number of elements between and including boundaries) subsequence_count = power_of_two[right_idx - left_idx]
Which of the two traversal algorithms (BFS and DFS) can be used to find whether two nodes are connected?
Recommended Readings
Tech Interview Pattern Two Pointers Introduction If you prefer videos here's a super quick introduction to Two Pointers div class responsive iframe iframe src https www youtube com embed xZ4AfXHQ1VQ title YouTube video player frameborder 0 allow accelerometer autoplay clipboard write encrypted media gyroscope picture in picture allowfullscreen iframe div Two pointers is a common interview
https assets algo monster cover_photos Binary_Search svg Binary Search Intuition Binary search is an efficient array search algorithm It works by narrowing down the search range by half each time If you have looked up a word in a physical dictionary you've already used binary search in real life Let's
Sorting Summary Comparisons We presented quite a few sorting algorithms and it is essential to know the advantages and disadvantages of each one The basic algorithms are easy to visualize and easy to learn for beginner programmers because of their simplicity As such they will suffice if you don't know any advanced
Want a Structured Path to Master System Design Too? Don’t Miss This!