2557. Maximum Number of Integers to Choose From a Range II
Problem Description
You need to choose integers from the range [1, n] to maximize the count of chosen integers while following these constraints:
- Each integer from
1toncan be chosen at most once - You cannot choose any integer that appears in the
bannedarray - The sum of all chosen integers must not exceed
maxSum
The goal is to return the maximum number of integers you can choose.
For example, if n = 7, banned = [2, 5], and maxSum = 10, you could choose integers like 1, 3, 4 (sum = 8) or 1, 3, 6 (sum = 10). The maximum count would be 3 integers.
The solution approach uses binary search to efficiently find the maximum number of consecutive integers that can be selected from each valid range. By adding boundaries 0 and n+1 to the banned list and sorting it, the code creates intervals of valid integers between banned numbers. For each interval [i+1, j-1], it uses binary search to find how many consecutive integers starting from i+1 can be selected without exceeding the remaining maxSum. The sum of the first k integers starting from i+1 is calculated using the arithmetic series formula: (first + last) * count / 2, which equals (i+1 + i+k) * k / 2.
Intuition
To maximize the count of integers we can choose, we should prioritize selecting smaller integers since they contribute less to the sum. This greedy approach allows us to fit more integers within the maxSum constraint.
The key insight is that between any two banned numbers, we have a continuous range of valid integers. For instance, if banned = [3, 7] and n = 10, we have valid ranges: [1, 2], [4, 6], and [8, 10]. Within each range, we want to select as many consecutive integers as possible starting from the smallest.
By adding boundaries 0 and n+1 to the banned list, we ensure that we also consider the ranges at the beginning [1, first_banned-1] and at the end [last_banned+1, n]. After sorting the banned list, each pair of adjacent banned numbers defines a range of selectable integers.
For each range [i+1, j-1] between banned numbers i and j, we need to find the maximum number of consecutive integers we can select starting from i+1 without exceeding the remaining maxSum. Since we're selecting consecutive integers, their sum forms an arithmetic sequence. If we select k integers starting from i+1, the sum is (i+1) + (i+2) + ... + (i+k) = (i+1 + i+k) * k / 2.
Binary search is perfect here because if we can select k integers without exceeding maxSum, we can also select any number less than k. Conversely, if k integers exceed maxSum, any number greater than k will also exceed it. This monotonic property makes binary search the optimal approach to find the maximum k for each range.
Learn more about Greedy, Binary Search and Sorting patterns.
Solution Implementation
1from typing import List 2from itertools import pairwise 3 4class Solution: 5 def maxCount(self, banned: List[int], n: int, maxSum: int) -> int: 6 # Add boundary values to handle edge cases 7 # 0 acts as left boundary, n+1 as right boundary 8 banned.extend([0, n + 1]) 9 10 # Remove duplicates and sort the banned numbers 11 sorted_banned = sorted(set(banned)) 12 13 # Track total count of numbers we can select 14 total_count = 0 15 16 # Process each gap between consecutive banned numbers 17 for current_banned, next_banned in pairwise(sorted_banned): 18 # Binary search to find maximum numbers we can take from this gap 19 # We can select from (current_banned + 1) to (next_banned - 1) 20 # Feasible condition: sum of k consecutive numbers <= maxSum 21 # We want the LAST true (maximum k where sum <= maxSum) 22 # Invert to find FIRST true where sum > maxSum, then subtract 1 23 left = 0 24 right = next_banned - current_banned - 1 # Maximum possible count in this gap 25 first_true_index = -1 26 27 # Find first k where sum of k numbers EXCEEDS maxSum 28 while left <= right: 29 mid = (left + right) // 2 30 31 # Calculate sum of 'mid' consecutive numbers starting from (current_banned + 1) 32 first_num = current_banned + 1 33 last_num = current_banned + mid 34 current_sum = (first_num + last_num) * mid // 2 35 36 if current_sum > maxSum: 37 # Sum exceeds budget - this is a "true" for exceeding 38 first_true_index = mid 39 right = mid - 1 40 else: 41 # Sum is within budget - keep searching right 42 left = mid + 1 43 44 # Maximum count we can take is one less than first exceeding, or the full range 45 max_count_in_gap = (first_true_index - 1) if first_true_index != -1 else (next_banned - current_banned - 1) 46 47 # Add the maximum count we can take from this gap 48 total_count += max_count_in_gap 49 50 # Update remaining sum budget 51 first_num = current_banned + 1 52 last_num = current_banned + max_count_in_gap 53 used_sum = (first_num + last_num) * max_count_in_gap // 2 54 maxSum -= used_sum 55 56 # Early termination if we've exhausted our sum budget 57 if maxSum <= 0: 58 break 59 60 return total_count 611class Solution { 2 public int maxCount(int[] banned, int n, long maxSum) { 3 // Create a set to store banned numbers and boundaries 4 Set<Integer> bannedSet = new HashSet<>(); 5 bannedSet.add(0); // Add left boundary 6 bannedSet.add(n + 1); // Add right boundary 7 8 // Add all banned numbers to the set 9 for (int bannedNumber : banned) { 10 bannedSet.add(bannedNumber); 11 } 12 13 // Convert set to sorted list for processing intervals 14 List<Integer> sortedBannedList = new ArrayList<>(bannedSet); 15 Collections.sort(sortedBannedList); 16 17 int totalCount = 0; 18 19 // Process each interval between consecutive banned numbers 20 for (int intervalIndex = 1; intervalIndex < sortedBannedList.size(); intervalIndex++) { 21 int leftBoundary = sortedBannedList.get(intervalIndex - 1); 22 int rightBoundary = sortedBannedList.get(intervalIndex); 23 24 // Binary search to find first k where sum EXCEEDS maxSum 25 int left = 0; 26 int right = rightBoundary - leftBoundary - 1; 27 int firstTrueIndex = -1; 28 29 while (left <= right) { 30 int mid = left + (right - left) / 2; 31 32 // Calculate sum of 'mid' consecutive integers starting from (leftBoundary + 1) 33 long sumOfMidNumbers = (leftBoundary + 1 + leftBoundary + mid) * 1L * mid / 2; 34 35 if (sumOfMidNumbers > maxSum) { 36 // Sum exceeds budget - this is feasible for "exceeding" 37 firstTrueIndex = mid; 38 right = mid - 1; 39 } else { 40 // Sum is within budget - keep searching right 41 left = mid + 1; 42 } 43 } 44 45 // Maximum count is one less than first exceeding, or the full range 46 int maxCountInGap = (firstTrueIndex != -1) ? (firstTrueIndex - 1) : (rightBoundary - leftBoundary - 1); 47 48 // Add the count of numbers we can take from this interval 49 totalCount += maxCountInGap; 50 51 // Subtract the sum of selected numbers from maxSum 52 maxSum -= (leftBoundary + 1 + leftBoundary + maxCountInGap) * 1L * maxCountInGap / 2; 53 54 // Early termination if we've exhausted the budget 55 if (maxSum <= 0) { 56 break; 57 } 58 } 59 60 return totalCount; 61 } 62} 631class Solution { 2public: 3 int maxCount(vector<int>& banned, int n, long long maxSum) { 4 // Add boundary values to simplify range processing 5 banned.push_back(0); // Lower boundary 6 banned.push_back(n + 1); // Upper boundary 7 8 // Sort and remove duplicates from banned array 9 sort(banned.begin(), banned.end()); 10 banned.erase(unique(banned.begin(), banned.end()), banned.end()); 11 12 int totalCount = 0; 13 14 // Process each gap between consecutive banned numbers 15 for (int idx = 1; idx < banned.size(); ++idx) { 16 int prevBanned = banned[idx - 1]; 17 int currBanned = banned[idx]; 18 19 // Binary search to find first k where sum EXCEEDS maxSum 20 int left = 0; 21 int right = currBanned - prevBanned - 1; 22 int firstTrueIndex = -1; 23 24 while (left <= right) { 25 int mid = left + (right - left) / 2; 26 27 // Calculate sum of 'mid' consecutive integers starting from (prevBanned + 1) 28 long long rangeSum = (prevBanned + 1 + prevBanned + mid) * 1LL * mid / 2; 29 30 if (rangeSum > maxSum) { 31 // Sum exceeds budget - this is feasible for "exceeding" 32 firstTrueIndex = mid; 33 right = mid - 1; 34 } else { 35 // Sum is within budget - keep searching right 36 left = mid + 1; 37 } 38 } 39 40 // Maximum count is one less than first exceeding, or the full range 41 int maxCountInGap = (firstTrueIndex != -1) ? (firstTrueIndex - 1) : (currBanned - prevBanned - 1); 42 43 // Add the maximum count found for this range 44 totalCount += maxCountInGap; 45 46 // Deduct the sum of selected numbers from maxSum 47 long long usedSum = (prevBanned + 1 + prevBanned + maxCountInGap) * 1LL * maxCountInGap / 2; 48 maxSum -= usedSum; 49 50 // Early termination if we've exhausted our sum budget 51 if (maxSum <= 0) { 52 break; 53 } 54 } 55 56 return totalCount; 57 } 58}; 591function maxCount(banned: number[], n: number, maxSum: number): number { 2 // Add boundary values to simplify range processing 3 banned.push(0); // Lower boundary 4 banned.push(n + 1); // Upper boundary 5 6 // Sort and remove duplicates from banned array 7 banned.sort((a, b) => a - b); 8 // Remove duplicates by filtering consecutive equal elements 9 const uniqueBanned: number[] = []; 10 for (let i = 0; i < banned.length; i++) { 11 if (i === 0 || banned[i] !== banned[i - 1]) { 12 uniqueBanned.push(banned[i]); 13 } 14 } 15 banned = uniqueBanned; 16 17 let totalCount = 0; 18 let remainingSum = maxSum; 19 20 // Process each gap between consecutive banned numbers 21 for (let idx = 1; idx < banned.length; idx++) { 22 const prevBanned = banned[idx - 1]; 23 const currBanned = banned[idx]; 24 25 // Binary search to find first k where sum EXCEEDS remainingSum 26 let left = 0; 27 let right = currBanned - prevBanned - 1; 28 let firstTrueIndex = -1; 29 30 while (left <= right) { 31 const mid = Math.floor((left + right) / 2); 32 33 // Calculate sum of 'mid' consecutive integers starting from (prevBanned + 1) 34 const rangeSum = (prevBanned + 1 + prevBanned + mid) * mid / 2; 35 36 if (rangeSum > remainingSum) { 37 // Sum exceeds budget - this is feasible for "exceeding" 38 firstTrueIndex = mid; 39 right = mid - 1; 40 } else { 41 // Sum is within budget - keep searching right 42 left = mid + 1; 43 } 44 } 45 46 // Maximum count is one less than first exceeding, or the full range 47 const maxCountInGap = (firstTrueIndex !== -1) ? (firstTrueIndex - 1) : (currBanned - prevBanned - 1); 48 49 // Add the maximum count found for this range 50 totalCount += maxCountInGap; 51 52 // Deduct the sum of selected numbers from remainingSum 53 const usedSum = (prevBanned + 1 + prevBanned + maxCountInGap) * maxCountInGap / 2; 54 remainingSum -= usedSum; 55 56 // Early termination if we've exhausted our sum budget 57 if (remainingSum <= 0) { 58 break; 59 } 60 } 61 62 return totalCount; 63} 64Solution Approach
The implementation follows these steps:
-
Add boundaries to banned array: We extend the
bannedarray by adding0andn + 1. This ensures we can handle the ranges at the beginning[1, first_banned-1]and end[last_banned+1, n]uniformly with other ranges. -
Deduplicate and sort: Convert
bannedto a set to remove duplicates, then sort it. This gives us a sorted list of unique banned values that we can iterate through in pairs. -
Process each valid range: Using
pairwise(ban), we iterate through consecutive pairs(i, j)in the sorted banned list. For each pair, the valid selectable range is[i+1, j-1]. -
Binary search for maximum selection: For each range, we use binary search to find the maximum number of consecutive integers we can select:
left = 0,right = j - i - 1(maximum possible integers in the range)- The mid-point check uses the arithmetic sum formula:
(i + 1 + i + mid) * mid // 2 - If this sum is within
maxSum, we can potentially select more integers (moveleft = mid) - Otherwise, we need to select fewer integers (move
right = mid - 1)
-
Update running totals: After finding the maximum selectable count
leftfor the current range:- Add
leftto our answerans - Subtract the sum of selected integers from
maxSum:maxSum -= (i + 1 + i + left) * left // 2
- Add
-
Early termination: If
maxSum <= 0, we can't select any more integers, so we break the loop.
The arithmetic sum formula (i + 1 + i + left) * left // 2 calculates the sum of left consecutive integers starting from i + 1. This equals the sum of integers from i + 1 to i + left, which is (first_term + last_term) * count / 2.
The binary search ensures we find the optimal number of integers to select from each range in O(log n) time, making the overall solution efficient.
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 the solution with n = 7, banned = [2, 5], and maxSum = 10.
Step 1: Add boundaries and sort
- Original banned:
[2, 5] - Add boundaries 0 and n+1:
[0, 2, 5, 8] - Already sorted, no duplicates to remove
Step 2: Process each valid range using pairwise iteration
Range 1: Between 0 and 2 → valid integers [1, 1]
- Binary search between 0 and 1 integers (since 2-0-1 = 1)
- Check mid=0: sum of 0 integers = 0 ≤ 10 ✓
- Check mid=1: sum of integer 1 = 1 ≤ 10 ✓
- Maximum: 1 integer (select 1)
- Update: ans = 1, maxSum = 10 - 1 = 9
Range 2: Between 2 and 5 → valid integers [3, 4]
- Binary search between 0 and 2 integers (since 5-2-1 = 2)
- Check mid=1: sum of integer 3 = 3 ≤ 9 ✓
- Check mid=2: sum of integers 3,4 = (3+4)×2/2 = 7 ≤ 9 ✓
- Maximum: 2 integers (select 3, 4)
- Update: ans = 1 + 2 = 3, maxSum = 9 - 7 = 2
Range 3: Between 5 and 8 → valid integers [6, 7]
- Binary search between 0 and 2 integers (since 8-5-1 = 2)
- Check mid=1: sum of integer 6 = 6 > 2 ✗
- Check mid=0: sum of 0 integers = 0 ≤ 2 ✓
- Maximum: 0 integers (cannot select any)
- Update: ans = 3, maxSum = 2
Final Answer: 3
The algorithm correctly identifies that we can select at most 3 integers: {1, 3, 4} with sum = 8, leaving room within maxSum = 10. Note that we prioritize smaller integers to maximize the count.
Time and Space Complexity
Time Complexity: O(n × log n)
The time complexity is dominated by the following operations:
sorted(set(banned)): Creating a set takesO(n)time, and sorting takesO(n log n)time wherenis the length of the banned list- The main loop iterates through consecutive pairs in the sorted banned list, which is
O(n)iterations - Inside each iteration, there's a binary search with
O(log(j - i))complexity. In the worst case,j - ican be proportional to the input parametern, making itO(log n)per iteration - Overall:
O(n log n)for sorting +O(n × log n)for the loop with binary search =O(n × log n)
Space Complexity: O(n)
The space complexity comes from:
banned.extend([0, n + 1]): This modifies the original list in-place but doesn't change the asymptotic spaceset(banned): Creates a new set with at mostn + 2elements, which isO(n)sorted(set(banned)): Creates a new sorted list from the set, alsoO(n)space- The
pairwiseiterator and other variables useO(1)additional space - Overall:
O(n)space complexity
Learn more about how to find time and space complexity quickly.
Common Pitfalls
1. Using Wrong Binary Search Template
Pitfall: Using while left < right with right = mid or left = mid variants instead of the standard template. This can lead to off-by-one errors or infinite loops.
Solution: Use the standard binary search template with while left <= right, first_true_index tracking, and right = mid - 1 when feasible:
left, right = 0, max_value first_true_index = -1 while left <= right: mid = (left + right) // 2 if feasible(mid): first_true_index = mid right = mid - 1 else: left = mid + 1
2. Integer Overflow in Sum Calculation
Pitfall: When calculating the arithmetic sum (first_num + last_num) * mid // 2, the intermediate multiplication can cause integer overflow for large values, even though Python handles big integers automatically. In languages like Java or C++, this would cause incorrect results.
Solution: Use 1L or 1LL multiplier in Java/C++ to force long arithmetic:
long sum = (first + last) * 1L * count / 2;
3. Not Handling Duplicate Banned Numbers
Pitfall: If you forget to deduplicate the banned array with set(), the same banned number appearing multiple times would create invalid "gaps" of size 0, leading to incorrect calculations.
Solution: Always convert to set before sorting:
sorted_banned = sorted(set(banned)) # Deduplication is crucial
4. Incorrect Gap Calculation
Pitfall: When calculating the maximum numbers in a gap between current_banned and next_banned, using next_banned - current_banned instead of next_banned - current_banned - 1 would include the banned numbers themselves.
Solution: Remember that the valid range is [current_banned + 1, next_banned - 1], so the count is:
right = next_banned - current_banned - 1 # Exclude both boundaries
5. Early Termination Logic Flaw
Pitfall: Breaking immediately when maxSum == 0 might miss cases where subsequent gaps have zeros that could still be selected (though this specific problem starts from 1, not 0).
Solution: Only break when maxSum < 0 or ensure the termination condition matches the problem constraints:
if maxSum <= 0: # Can't afford any positive integers break
A person thinks of a number between 1 and 1000. You may ask any number questions to them, provided that the question can be answered with either "yes" or "no".
What is the minimum number of questions you needed to ask so that you are guaranteed to know the number that the person is thinking?
Recommended Readings
Greedy Introduction div class responsive iframe iframe src https www youtube com embed WTslqPbj7I title YouTube video player frameborder 0 allow accelerometer autoplay clipboard write encrypted media gyroscope picture in picture web share allowfullscreen iframe div When do we use greedy Greedy algorithms tend to solve optimization problems Typically they will ask you to calculate the max min of some value Commonly you may see this phrased in the problem as max min longest shortest largest smallest etc These keywords can be identified by just scanning
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!