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
i
to 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 > i
could potentially be the maximum, as long asnums[i] + nums[j] ≤ target
. -
Finding the boundary: For a fixed minimum
nums[i]
, there's a rightmost indexj
wherenums[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
i
toj
, how many subsequences are there? We must includenums[i]
(the minimum) andnums[j]
(the maximum), but for every element between them (indicesi+1
toj-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 Approach
The implementation follows the sorting and binary search strategy outlined in the reference approach:
Step 1: Preprocessing
- Sort the array
nums
in ascending order - Pre-compute powers of 2 up to
n
and 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: Use binary search to find the rightmost index
j
wherenums[i] + nums[j] ≤ target
bisect_right(nums, target - nums[i], i + 1)
finds the insertion point fortarget - nums[i]
- Subtract 1 to get the actual index of the largest valid element
- Search starts from
i + 1
since we needj > i
-
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 - 1
elements 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
j
wherenums[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 = 2
subsequences:[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.
Solution Implementation
1class Solution:
2 def numSubseq(self, nums: List[int], target: int) -> int:
3 # Modulo value for preventing integer overflow
4 MOD = 10**9 + 7
5
6 # Sort the array to easily identify min and max in subsequences
7 nums.sort()
8 n = len(nums)
9
10 # Precompute powers of 2 for efficiency
11 # power_of_two[i] represents 2^i mod MOD
12 power_of_two = [1] * n
13 for i in range(1, n):
14 power_of_two[i] = (power_of_two[i - 1] * 2) % MOD
15
16 result = 0
17
18 # Iterate through each element as the minimum of a subsequence
19 for left_idx, min_val in enumerate(nums):
20 # If minimum element doubled exceeds target, no valid subsequences exist
21 if min_val * 2 > target:
22 break
23
24 # Find the rightmost index where nums[right_idx] + min_val <= target
25 # Using binary search starting from left_idx + 1
26 right_idx = bisect_right(nums, target - min_val, left_idx + 1) - 1
27
28 # Count subsequences with min_val as minimum and any element
29 # between left_idx+1 and right_idx (inclusive)
30 # Number of such subsequences is 2^(right_idx - left_idx)
31 subsequence_count = power_of_two[right_idx - left_idx]
32 result = (result + subsequence_count) % MOD
33
34 return result
35
1class Solution {
2 public int numSubseq(int[] nums, int target) {
3 // Sort the array to easily find valid subsequences
4 Arrays.sort(nums);
5
6 // Constants for modulo arithmetic to prevent overflow
7 final int MOD = 1_000_000_007;
8 int n = nums.length;
9
10 // Precompute powers of 2 for subsequence counting
11 // powerOfTwo[i] = 2^i % MOD
12 int[] powerOfTwo = new int[n + 1];
13 powerOfTwo[0] = 1;
14 for (int i = 1; i <= n; i++) {
15 powerOfTwo[i] = (powerOfTwo[i - 1] * 2) % MOD;
16 }
17
18 // Count valid subsequences
19 int result = 0;
20
21 // For each element as the minimum of a subsequence
22 for (int minIndex = 0; minIndex < n && nums[minIndex] * 2 <= target; minIndex++) {
23 // Find the rightmost element that can be the maximum
24 // such that nums[minIndex] + nums[maxIndex] <= target
25 int maxValue = target - nums[minIndex];
26 int maxIndexPlusOne = binarySearchUpperBound(nums, maxValue, minIndex + 1);
27 int maxIndex = maxIndexPlusOne - 1;
28
29 // Add number of valid subsequences with nums[minIndex] as minimum
30 // There are 2^(maxIndex - minIndex) such subsequences
31 result = (result + powerOfTwo[maxIndex - minIndex]) % MOD;
32 }
33
34 return result;
35 }
36
37 /**
38 * Binary search to find the first index where nums[index] > targetValue
39 * @param nums sorted array to search in
40 * @param targetValue the value to compare against
41 * @param startIndex the starting index for the search
42 * @return the index of the first element greater than targetValue
43 */
44 private int binarySearchUpperBound(int[] nums, int targetValue, int startIndex) {
45 int left = startIndex;
46 int right = nums.length;
47
48 while (left < right) {
49 int mid = (left + right) >>> 1; // Use unsigned shift to avoid overflow
50
51 if (nums[mid] > targetValue) {
52 right = mid;
53 } else {
54 left = mid + 1;
55 }
56 }
57
58 return left;
59 }
60}
61
1class Solution {
2public:
3 int numSubseq(vector<int>& nums, int target) {
4 // Sort the array to use two-pointer technique
5 sort(nums.begin(), nums.end());
6
7 const int MOD = 1e9 + 7;
8 int n = nums.size();
9
10 // Precompute powers of 2 to avoid repeated calculations
11 // power[i] represents 2^i mod MOD
12 vector<int> power(n + 1);
13 power[0] = 1;
14 for (int i = 1; i <= n; ++i) {
15 power[i] = (power[i - 1] * 2) % MOD;
16 }
17
18 int result = 0;
19
20 // For each starting position i, find the rightmost position j
21 // where nums[i] + nums[j] <= target
22 for (int i = 0; i < n && nums[i] * 2 <= target; ++i) {
23 // Find the largest index j where nums[i] + nums[j] <= target
24 int j = upper_bound(nums.begin() + i + 1, nums.end(), target - nums[i]) - nums.begin() - 1;
25
26 // Between indices i and j, we have (j - i) elements after index i
27 // Each of these elements can either be included or excluded
28 // So we have 2^(j - i) subsequences with nums[i] as minimum
29 result = (result + power[j - i]) % MOD;
30 }
31
32 return result;
33 }
34};
35
1/**
2 * Counts the number of non-empty subsequences where min + max <= target
3 * @param nums - Array of integers to find subsequences from
4 * @param target - Target sum constraint for min + max elements
5 * @returns Number of valid subsequences modulo 10^9 + 7
6 */
7function numSubseq(nums: number[], target: number): number {
8 // Sort array to enable two-pointer approach
9 nums.sort((a, b) => a - b);
10
11 const MOD = 1e9 + 7;
12 const length = nums.length;
13
14 // Precompute powers of 2 to avoid repeated calculations
15 // powers[i] represents 2^i mod MOD
16 const powers: number[] = Array(length + 1).fill(1);
17 for (let i = 1; i <= length; i++) {
18 powers[i] = (powers[i - 1] * 2) % MOD;
19 }
20
21 let result = 0;
22
23 // For each element as minimum, find maximum valid element
24 for (let minIndex = 0; minIndex < length && nums[minIndex] * 2 <= target; minIndex++) {
25 // Find the rightmost index where nums[minIndex] + nums[maxIndex] <= target
26 const maxValue = target - nums[minIndex];
27 const maxIndex = search(nums, maxValue, minIndex + 1) - 1;
28
29 // If valid range exists, add number of subsequences
30 if (maxIndex >= minIndex) {
31 // Number of subsequences with nums[minIndex] as minimum
32 // is 2^(maxIndex - minIndex) since we can include/exclude each element between them
33 result = (result + powers[maxIndex - minIndex]) % MOD;
34 }
35 }
36
37 return result;
38}
39
40/**
41 * Binary search to find the first index where nums[index] > targetValue
42 * @param nums - Sorted array to search in
43 * @param targetValue - Value to search for
44 * @param startIndex - Starting index for the search
45 * @returns Index of first element greater than targetValue
46 */
47function search(nums: number[], targetValue: number, startIndex: number): number {
48 let left = startIndex;
49 let right = nums.length;
50
51 // Binary search for upper bound
52 while (left < right) {
53 const mid = (left + right) >> 1; // Equivalent to Math.floor((left + right) / 2)
54
55 if (nums[mid] > targetValue) {
56 right = mid;
57 } else {
58 left = mid + 1;
59 }
60 }
61
62 return left;
63}
64
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
f
takesO(n)
time - The main loop iterates through each element once, taking
O(n)
iterations - Inside each iteration,
bisect_right
performs 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
f
storesn + 1
precomputed 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. Incorrect Power of 2 Calculation Without Modulo
A critical mistake is computing powers of 2 without applying modulo during the precomputation phase. This 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
2. Off-by-One Error in Binary Search
Forgetting to subtract 1 from bisect_right
result leads to including an invalid element that makes the sum exceed the target.
Incorrect Implementation:
# This includes an element where nums[i] + nums[right_idx] > target right_idx = bisect_right(nums, target - nums[i], left_idx + 1)
Correct Implementation:
# Subtract 1 to get the actual last valid index right_idx = bisect_right(nums, target - nums[i], left_idx + 1) - 1
3. Wrong Search Range in Binary Search
Starting the binary search from index left_idx
instead of left_idx + 1
causes the algorithm to count single-element subsequences incorrectly or double-count certain subsequences.
Incorrect Implementation:
# This searches from the current minimum element itself right_idx = bisect_right(nums, target - nums[i], left_idx) - 1
Correct Implementation:
# Start search from the next element after minimum right_idx = bisect_right(nums, target - nums[i], left_idx + 1) - 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 following uses divide and conquer strategy?
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
Binary Search Speedrun For each of the Speedrun questions you will be given a binary search related problem and a corresponding multiple choice question The multiple choice questions are related to the techniques and template s introduced in the binary search section It's recommended that you have gone through at
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!