Facebook Pixel

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 1 to n can be chosen at most once
  • You cannot choose any integer that appears in the banned array
  • 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.

Quick Interview Experience
Help others by sharing your interview experience
Have you seen this problem before?

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 Approach

The implementation follows these steps:

  1. Add boundaries to banned array: We extend the banned array by adding 0 and n + 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.

  2. Deduplicate and sort: Convert banned to 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.

  3. 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].

  4. 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 (move left = mid)
    • Otherwise, we need to select fewer integers (move right = mid - 1)
  5. Update running totals: After finding the maximum selectable count left for the current range:

    • Add left to our answer ans
    • Subtract the sum of selected integers from maxSum: maxSum -= (i + 1 + i + left) * left // 2
  6. 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 Evaluator

Example 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.

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            left = 0
21            right = next_banned - current_banned - 1  # Maximum possible count in this gap
22          
23            while left < right:
24                # Use ceiling division for binary search
25                mid = (left + right + 1) >> 1
26              
27                # Calculate sum of 'mid' consecutive numbers starting from (current_banned + 1)
28                # Using arithmetic sequence sum formula: n * (first + last) / 2
29                first_num = current_banned + 1
30                last_num = current_banned + mid
31                current_sum = (first_num + last_num) * mid // 2
32              
33                if current_sum <= maxSum:
34                    # Can take at least 'mid' numbers, try for more
35                    left = mid
36                else:
37                    # Can't take 'mid' numbers, try fewer
38                    right = mid - 1
39          
40            # Add the maximum count we can take from this gap
41            total_count += left
42          
43            # Update remaining sum budget
44            first_num = current_banned + 1
45            last_num = current_banned + left
46            used_sum = (first_num + last_num) * left // 2
47            maxSum -= used_sum
48          
49            # Early termination if we've exhausted our sum budget
50            if maxSum <= 0:
51                break
52      
53        return total_count
54
1class 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 maximum numbers we can take from this interval
25            int left = 0;
26            int right = rightBoundary - leftBoundary - 1;  // Maximum possible count in interval
27          
28            while (left < right) {
29                int mid = (left + right + 1) >>> 1;  // Use unsigned right shift to avoid overflow
30              
31                // Calculate sum of 'mid' consecutive integers starting from (leftBoundary + 1)
32                // Sum formula: sum of arithmetic sequence = (first + last) * count / 2
33                long sumOfMidNumbers = (leftBoundary + 1 + leftBoundary + mid) * 1L * mid / 2;
34              
35                if (sumOfMidNumbers <= maxSum) {
36                    left = mid;  // Can take at least 'mid' numbers
37                } else {
38                    right = mid - 1;  // Need to take fewer numbers
39                }
40            }
41          
42            // Add the count of numbers we can take from this interval
43            totalCount += left;
44          
45            // Subtract the sum of selected numbers from maxSum
46            maxSum -= (leftBoundary + 1 + leftBoundary + left) * 1L * left / 2;
47          
48            // Early termination if we've exhausted the budget
49            if (maxSum <= 0) {
50                break;
51            }
52        }
53      
54        return totalCount;
55    }
56}
57
1class 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 for maximum count of consecutive numbers we can take
20            // from range [prevBanned + 1, currBanned - 1]
21            int left = 0;
22            int right = currBanned - prevBanned - 1;  // Maximum possible count in this range
23          
24            while (left < right) {
25                // Use upper mid to avoid infinite loop
26                int mid = left + (right - left + 1) / 2;
27              
28                // Calculate sum of 'mid' consecutive integers starting from (prevBanned + 1)
29                // Sum formula: sum of arithmetic sequence from (prevBanned + 1) to (prevBanned + mid)
30                // = (first + last) * count / 2
31                long long rangeSum = (prevBanned + 1 + prevBanned + mid) * 1LL * mid / 2;
32              
33                if (rangeSum <= maxSum) {
34                    left = mid;  // Can take at least 'mid' numbers, try for more
35                } else {
36                    right = mid - 1;  // Can't take 'mid' numbers, try fewer
37                }
38            }
39          
40            // Add the maximum count found for this range
41            totalCount += left;
42          
43            // Deduct the sum of selected numbers from maxSum
44            long long usedSum = (prevBanned + 1 + prevBanned + left) * 1LL * left / 2;
45            maxSum -= usedSum;
46          
47            // Early termination if we've exhausted our sum budget
48            if (maxSum <= 0) {
49                break;
50            }
51        }
52      
53        return totalCount;
54    }
55};
56
1function 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 for maximum count of consecutive numbers we can take
26        // from range [prevBanned + 1, currBanned - 1]
27        let left = 0;
28        let right = currBanned - prevBanned - 1;  // Maximum possible count in this range
29      
30        while (left < right) {
31            // Use upper mid to avoid infinite loop
32            const mid = left + Math.floor((right - left + 1) / 2);
33          
34            // Calculate sum of 'mid' consecutive integers starting from (prevBanned + 1)
35            // Sum formula: sum of arithmetic sequence from (prevBanned + 1) to (prevBanned + mid)
36            // = (first + last) * count / 2
37            const rangeSum = (prevBanned + 1 + prevBanned + mid) * mid / 2;
38          
39            if (rangeSum <= remainingSum) {
40                left = mid;  // Can take at least 'mid' numbers, try for more
41            } else {
42                right = mid - 1;  // Can't take 'mid' numbers, try fewer
43            }
44        }
45      
46        // Add the maximum count found for this range
47        totalCount += left;
48      
49        // Deduct the sum of selected numbers from remainingSum
50        const usedSum = (prevBanned + 1 + prevBanned + left) * left / 2;
51        remainingSum -= usedSum;
52      
53        // Early termination if we've exhausted our sum budget
54        if (remainingSum <= 0) {
55            break;
56        }
57    }
58  
59    return totalCount;
60}
61

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 takes O(n) time, and sorting takes O(n log n) time where n is 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 - i can be proportional to the input parameter n, making it O(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 space
  • set(banned): Creates a new set with at most n + 2 elements, which is O(n)
  • sorted(set(banned)): Creates a new sorted list from the set, also O(n) space
  • The pairwise iterator and other variables use O(1) additional space
  • Overall: O(n) space complexity

Learn more about how to find time and space complexity quickly.

Common Pitfalls

1. 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: Rearrange the formula to minimize overflow risk:

# Instead of: (first_num + last_num) * mid // 2
# Use: first_num * mid + mid * (mid - 1) // 2

This calculates the same sum but reduces the magnitude of intermediate values.

2. Binary Search Boundary Error

Pitfall: Using the wrong binary search pattern when left < right. The code uses mid = (left + right + 1) >> 1 (ceiling division) with left = mid update, but forgetting the +1 would cause an infinite loop when left = right - 1.

Solution: Always match the binary search pattern correctly:

  • If using left = mid, then mid = (left + right + 1) // 2 (ceiling)
  • If using right = mid, then mid = (left + right) // 2 (floor)

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
Discover Your Strengths and Weaknesses: Take Our 5-Minute Quiz to Tailor Your Study Plan:

Given a sorted array of integers and an integer called target, find the element that equals to the target and return its index. Select the correct code that fills the ___ in the given code snippet.

1def binary_search(arr, target):
2    left, right = 0, len(arr) - 1
3    while left ___ right:
4        mid = (left + right) // 2
5        if arr[mid] == target:
6            return mid
7        if arr[mid] < target:
8            ___ = mid + 1
9        else:
10            ___ = mid - 1
11    return -1
12
1public static int binarySearch(int[] arr, int target) {
2    int left = 0;
3    int right = arr.length - 1;
4
5    while (left ___ right) {
6        int mid = left + (right - left) / 2;
7        if (arr[mid] == target) return mid;
8        if (arr[mid] < target) {
9            ___ = mid + 1;
10        } else {
11            ___ = mid - 1;
12        }
13    }
14    return -1;
15}
16
1function binarySearch(arr, target) {
2    let left = 0;
3    let right = arr.length - 1;
4
5    while (left ___ right) {
6        let mid = left + Math.trunc((right - left) / 2);
7        if (arr[mid] == target) return mid;
8        if (arr[mid] < target) {
9            ___ = mid + 1;
10        } else {
11            ___ = mid - 1;
12        }
13    }
14    return -1;
15}
16

Recommended Readings

Want a Structured Path to Master System Design Too? Don’t Miss This!

Load More