2557. Maximum Number of Integers to Choose From a Range II
Problem Description
We are given an integer array banned
, a positive integer n
indicating the upper limit of an integer range starting from 1, and a positive integer maxSum
which serves as the cap for the sum of integers we can select. The task is to choose a collection of distinct integers within this range from 1 to n
, ensuring two conditions: first, that none of the integers are present in the banned
array, and second, that the cumulative sum of all chosen integers does not exceed maxSum
. The objective is to maximize the number of integers chosen while adhering to the stated constraints.
Intuition
To maximize the number of chosen integers, while considering the maxSum
limitation and banned list, we must take a strategic approach in picking the integers. A naive approach might be to start from 1 and keep adding the next non-banned integer until we reach or exceed maxSum
. However, this might not optimize for the maximum number of integers since larger values will use up the allowed sum more quickly.
Instead, the intuition is to proceed in a way that we try to include as many small non-banned numbers as possible because smaller numbers have smaller sums, enabling us to pick more numbers while staying under maxSum
.
We can achieve this by:
- Extending the
banned
list with two sentinel values, 0 andn+1
, which represent the lower and upper boundaries beyond which we can't select integers. - Sorting the extended
banned
list to be able to iterate through banned ranges efficiently. - Using binary search to find the maximum count of integers that can be selected between each pair of consecutive banned numbers, without exceeding
maxSum
.
This can be seen in the provided solution code where the banned
list is augmented and then sorted. The pairwise
function (assumed to be similar to itertools.pairwise) iterates through adjacent elements providing pairs of banned integers so we can calculate the potential numbers between them.
The binary search within the loop efficiently finds the maximum number of selectable integers in each range. If adding one more integer exceeds maxSum
, the binary search moves left, else it moves right to expand the count. Once the range is found, we add it to the ans
tally and deduct the sum of those integers from maxSum
to ensure we don't exceed the overall allowed sum.
The answer is returned as soon as maxSum
is not enough to add more integers, guaranteeing the focus on maximizing quantity with the smallest possible numbers while adhering to maxSum
.
Learn more about Greedy, Binary Search and Sorting patterns.
Solution Approach
The code below implements a strategic method to solve the problem by maximizing the number of integers chosen within the given constraints. Here's a deeper dive into the solution's methodology:
First, the solution uses both the concept of sorting and binary search, combining these algorithms for efficient computation. The banned
list is modified to include two extra elements, 0
and n + 1
, to handle edge cases seamlessly.
The data structures used include:
- A list (
banned
) that contains the original banned numbers plus the two sentinels (0 andn + 1
). - A set, to ensure all elements in
banned
are unique. - A sorted list version of that set, to allow for binary search operations.
The code then traverses the sorted banned
list, searching for the maximum number of selectable integers between each pair of consecutive banned numbers (now including the sentinels). This is done using the pairwise
iteration which is assumed to yield a tuple (i, j)
where i
and j
are adjacent banned numbers.
For each pair (i, j)
, the algorithm finds the potential gap (j - i - 1
) between them to determine the number of selectable integers. It then employs binary search within this gap to find the maximum count of integers (left
) which can be chosen without exceeding maxSum
. The binary search checks if the sum of an arithmetic series from i + 1
to i + mid
is less than or equal to maxSum
.
The arithmetic series sum is calculated with the formula:
(i + 1 + i + mid) * mid / 2
This sum formula computes the sum of the first mid
terms of the natural numbers starting at i + 1
.
After finding the left
value for each range, the algorithm updates the answer (ans
) with the count of new integers added, and subtracts the sum of those integers from maxSum
.
The process stops when maxSum
becomes zero or negative, indicating that no additional integers can be chosen without exceeding the maximum sum constraint at which point the current value of ans
is returned.
Through this approach, the solution efficiently maximizes the number of integers chosen without exceeding maxSum
and while respecting the banned
list.
Ready to land your dream job?
Unlock your dream job with a 2-minute evaluator for a personalized learning plan!
Start EvaluatorExample Walkthrough
Let's consider an example to illustrate the solution approach.
Suppose we are given the following parameters:
banned = [3, 6, 10]
n = 15
maxSum = 25
Now, let's use the solution approach step by step:
-
Extend and Sort the
banned
List: Thebanned
array is extended to include two sentinel values, which here would be0
andn+1 = 16
. After including these, thebanned
array is sorted. The updatedbanned
list looks like this:[0, 3, 6, 10, 16]
. -
Iterate Using Pairwise: Using the
pairwise
function, we obtain the adjacent pairs of banned numbers:(0, 3)
,(3, 6)
,(6, 10)
, and(10, 16)
. -
Binary Search on Each Range: For each pair
(i, j)
, we perform a binary search to find the maximum count of integers that can be selected betweeni+1
andj-1
without exceedingmaxSum
. Here's how it works for each pair:-
For
(0, 3)
, the selectable range is1
and2
. Since the sum1 + 2
is3
, which is less thanmaxSum
, we include both numbers. Now,maxSum
is reduced to22
. -
For
(3, 6)
, the selectable range is4
and5
. The sum4 + 5
is9
, which is within the remainingmaxSum
of22
. We include these, andmaxSum
reduces to13
. -
Next, for
(6, 10)
, the selectable range is7
,8
, and9
. The binary search finds that we can only take7
and8
without exceeding the newmaxSum
of13
, because their sum is15
, and adding9
would bring the total to24
, over the cap. So we add7
and8
and reducemaxSum
to13 - (7 + 8) = -2
. NowmaxSum
is negative. -
At this point, we cannot choose any more numbers because the remaining
maxSum
is not enough to include more integers. We stop and do not proceed to the pair(10, 16)
.
-
-
End Result: The maximum number of integers chosen without exceeding
maxSum
and avoiding thebanned
list is[1, 2, 4, 5, 7, 8]
. Theans
would be6
, which is the count of these chosen integers.
This example walk-through demonstrates how the problem is solved using the described approach, ensuring that we maximize the number of integers chosen within the constraints.
Solution Implementation
1class Solution:
2 def maxCount(self, banned: List[int], n: int, maxSum: int) -> int:
3 # Extend the banned list to include 0 and n+1 to simplify range calculations
4 banned.extend([0, n + 1])
5 # Ensure the banned list is unique and sorted
6 banned = sorted(set(banned))
7
8 # Initialize the answer to 0
9 answer = 0
10
11 # Loop through each pair of consecutive banned numbers (Using pairwise requires itertools in Python 3.8+)
12 for lower_bound, upper_bound in zip(banned, banned[1:]):
13 # We use binary search to find the maximum count of numbers
14 # between a pair of banned numbers that we can sum without exceeding maxSum
15 left, right = 0, upper_bound - lower_bound - 1
16
17 while left < right:
18 # Calculate the midpoint for binary search
19 mid = (left + right + 1) // 2
20
21 # Calculate the sum of an arithmetic series from lower_bound+1 to lower_bound+mid
22 if ((lower_bound + 1 + lower_bound + mid) * mid) // 2 <= maxSum:
23 # If the sum is less than or equal to maxSum, move the left bound up
24 left = mid
25 else:
26 # Otherwise, move the right bound down
27 right = mid - 1
28
29 # Add the count of numbers found to the answer
30 answer += left
31 # Decrease maxSum by the sum of numbers we've added to the answer
32 maxSum -= ((lower_bound + 1 + lower_bound + left) * left) // 2
33
34 # If maxSum becomes zero or negative, we cannot add any more numbers
35 if maxSum <= 0:
36 break
37
38 # Return the total count of numbers we can sum
39 return answer
40
1class Solution {
2 // Method to calculate the maximum count of consecutive numbers not exceeding maxSum
3 // with the given constraints.
4 public int maxCount(int[] banned, int n, long maxSum) {
5 // Create a hash set to store banned numbers along with the boundaries.
6 Set<Integer> bannedIndicesSet = new HashSet<>();
7 bannedIndicesSet.add(0); // Add lower boundary
8 bannedIndicesSet.add(n + 1); // Add upper boundary
9
10 // Add all banned numbers to the set
11 for (int bannedNumber : banned) {
12 bannedIndicesSet.add(bannedNumber);
13 }
14
15 // Convert the set to a list and sort it to process intervals between banned numbers.
16 List<Integer> bannedIndicesList = new ArrayList<>(bannedIndicesSet);
17 Collections.sort(bannedIndicesList);
18
19 // Initialize the answer which will store the maximum count of consecutive numbers
20 int maxCount = 0;
21
22 for (int k = 1; k < bannedIndicesList.size(); ++k) {
23 // Get the interval boundaries from the sorted list
24 int intervalStart = bannedIndicesList.get(k - 1);
25 int intervalEnd = bannedIndicesList.get(k);
26
27 // Initialize binary search bounds
28 int left = 0;
29 int right = intervalEnd - intervalStart - 1;
30
31 while (left < right) {
32 // Use unsigned right shift for division by 2 to avoid negative overflow
33 int mid = (left + right + 1) >>> 1;
34
35 // Check if the sum of consecutive numbers from intervalStart+1 to intervalStart+mid
36 // fits in the maxSum using the arithmetic progression sum formula
37 if ((intervalStart + 1 + intervalStart + mid) * 1L * mid / 2 <= maxSum) {
38 left = mid;
39 } else {
40 right = mid - 1;
41 }
42 }
43
44 // Update maxCount and decrease maxSum by the sum of consecutive numbers found
45 maxCount += left;
46 maxSum -= (intervalStart + 1 + intervalStart + left) * 1L * left / 2;
47
48 // If maxSum is exhausted, break out of the loop
49 if (maxSum <= 0) {
50 break;
51 }
52 }
53
54 // Return the calculated maximum count
55 return maxCount;
56 }
57}
58
1#include <vector>
2#include <algorithm>
3
4class Solution {
5public:
6 // This function calculates the maximum count of numbers that can be added
7 // to the intervals between banned numbers without exceeding maxSum.
8 int maxCount(std::vector<int>& banned, int n, long long maxSum) {
9 // Add boundary markers for the start and end of possible number range.
10 banned.push_back(0); // Add a banned number at the start.
11 banned.push_back(n + 1); // Add a banned number at the end.
12
13 // Sort the banned numbers to process them in order.
14 std::sort(banned.begin(), banned.end());
15
16 // Remove duplicate entries if any, to handle only unique banned numbers.
17 banned.erase(std::unique(banned.begin(), banned.end()), banned.end());
18
19 int answer = 0; // Initialize the answer to zero.
20
21 // Iterate through the intervals between banned numbers.
22 for (int index = 1; index < banned.size(); ++index) {
23 int start = banned[index - 1], end = banned[index];
24 int low = 0, high = end - start - 1; // Low and high limits for current interval.
25
26 // Perform binary search within the interval to find the max count.
27 while (low < high) {
28 int mid = low + ((high - low + 1) / 2);
29 // Calculate the sum of arithmetic progression.
30 if ((start + 1 + start + mid) * 1LL * mid / 2 <= maxSum) {
31 low = mid; // If the sum is within limit, search right side.
32 } else {
33 high = mid - 1; // If sum exceeds limit, search left side.
34 }
35 }
36
37 answer += low; // Add the found count to the answer.
38 // Subtract the sum of the found numbers from maxSum.
39 maxSum -= (start + 1 + start + low) * 1LL * low / 2;
40
41 if (maxSum <= 0) { // If there's no sum left to add numbers, break out.
42 break;
43 }
44 }
45
46 return answer; // Return the total count of numbers that can be added.
47 }
48};
49
1function maxCount(banned: number[], n: number, maxSum: number): number {
2 // Add boundary markers for the start and end of the possible number range.
3 banned.push(0); // Add a banned number at the start.
4 banned.push(n + 1); // Add a banned number at the end.
5
6 // Sort the banned numbers to process them in order.
7 banned.sort((a, b) => a - b);
8
9 // Remove duplicate entries if any, to handle only unique banned numbers.
10 banned = Array.from(new Set(banned));
11
12 let answer = 0; // Initialize the answer to zero.
13
14 // Iterate through the intervals between banned numbers.
15 for (let index = 1; index < banned.length; ++index) {
16 let start = banned[index - 1], end = banned[index];
17 let low = 0, high = end - start - 1; // Low and high limits for the current interval.
18
19 // Perform binary search within the interval to find the max count.
20 while (low < high) {
21 let mid = low + Math.floor((high - low + 1) / 2);
22 // Calculate the sum of the arithmetic progression.
23 if ((start + 1 + start + mid) * mid / 2 <= maxSum) {
24 low = mid; // If the sum is within the limit, search the right side.
25 } else {
26 high = mid - 1; // If sum exceeds the limit, search the left side.
27 }
28 }
29
30 answer += low; // Add the found count to the answer.
31 // Subtract the sum of the found numbers from maxSum.
32 maxSum -= (start + 1 + start + low) * low / 2;
33
34 if (maxSum <= 0) { // If there's no sum left to add numbers, break out.
35 break;
36 }
37 }
38
39 return answer; // Return the total count of numbers that can be added.
40}
41
42// Example usage:
43// let bannedNumbers = [3, 6, 14];
44// let n = 15;
45// let maxSum = 25;
46// console.log(maxCount(bannedNumbers, n, maxSum)); // Should print the result of the function.
47
Time and Space Complexity
Time Complexity
The time complexity of the code is determined by mainly two operations: sorting the banned
list and performing a binary search for each pair in the list after incorporating the additional elements and removing duplicates.
-
Sorting the
banned
list has a time complexity ofO(K * log(K))
, whereK
is the number of elements in thebanned
list after extending it with[0, n + 1]
. Sinceban
is constructed by convertingbanned
to a set and sorting, the number of elementsK
is at most the original length ofbanned
plus 2. -
The
pairwise
function results inO(K - 1)
iterations since it will create pairs of adjacent elements in the sortedban
list. -
For each pair
(i, j)
produced bypairwise
, there is a binary search running to determine how many integers can be counted betweeni
andj
without exceedingmaxSum
. The binary search runs inO(log(N))
time in the worst case, whereN
is the difference(j - i - 1)
.
Therefore, if M
is the maximum value in the list before sorting (and after adding 0
and n + 1
), then the worst-case scenario for the binary search is O(log(M))
time for each of K - 1
iterations.
Combining these, the total time complexity is O(K * log(K) + (K - 1) * log(M))
= O(K * log(K) + K * log(M))
. Since K
can be at most n + 2
, the time complexity in terms of n
is O((n + 2) * log(n + 2) + (n + 1) * log(n))
.
Space Complexity
The space complexity of the code involves the additional space used by the ban
list and the space for the variables used in the binary search.
-
The
ban
list stores at mostK
elements, which is at most the length ofbanned
plus 2, thus giving us a space complexity ofO(K)
. -
The variables used in the binary search (
left
,right
,mid
,ans
, andmaxSum
) require constant spaceO(1)
.
Therefore, the space complexity of the algorithm is O(K)
. This is O(n)
in terms of the maximum possible length of the ban
list when n
is large compared to the number of banned elements.
Learn more about how to find time and space complexity quickly using problem constraints.
What's the output of running the following function using input [30, 20, 10, 100, 33, 12]
?
1def fun(arr: List[int]) -> List[int]:
2 import heapq
3 heapq.heapify(arr)
4 res = []
5 for i in range(3):
6 res.append(heapq.heappop(arr))
7 return res
8
1public static int[] fun(int[] arr) {
2 int[] res = new int[3];
3 PriorityQueue<Integer> heap = new PriorityQueue<>();
4 for (int i = 0; i < arr.length; i++) {
5 heap.add(arr[i]);
6 }
7 for (int i = 0; i < 3; i++) {
8 res[i] = heap.poll();
9 }
10 return res;
11}
12
1class HeapItem {
2 constructor(item, priority = item) {
3 this.item = item;
4 this.priority = priority;
5 }
6}
7
8class MinHeap {
9 constructor() {
10 this.heap = [];
11 }
12
13 push(node) {
14 // insert the new node at the end of the heap array
15 this.heap.push(node);
16 // find the correct position for the new node
17 this.bubble_up();
18 }
19
20 bubble_up() {
21 let index = this.heap.length - 1;
22
23 while (index > 0) {
24 const element = this.heap[index];
25 const parentIndex = Math.floor((index - 1) / 2);
26 const parent = this.heap[parentIndex];
27
28 if (parent.priority <= element.priority) break;
29 // if the parent is bigger than the child then swap the parent and child
30 this.heap[index] = parent;
31 this.heap[parentIndex] = element;
32 index = parentIndex;
33 }
34 }
35
36 pop() {
37 const min = this.heap[0];
38 this.heap[0] = this.heap[this.size() - 1];
39 this.heap.pop();
40 this.bubble_down();
41 return min;
42 }
43
44 bubble_down() {
45 let index = 0;
46 let min = index;
47 const n = this.heap.length;
48
49 while (index < n) {
50 const left = 2 * index + 1;
51 const right = left + 1;
52
53 if (left < n && this.heap[left].priority < this.heap[min].priority) {
54 min = left;
55 }
56 if (right < n && this.heap[right].priority < this.heap[min].priority) {
57 min = right;
58 }
59 if (min === index) break;
60 [this.heap[min], this.heap[index]] = [this.heap[index], this.heap[min]];
61 index = min;
62 }
63 }
64
65 peek() {
66 return this.heap[0];
67 }
68
69 size() {
70 return this.heap.length;
71 }
72}
73
74function fun(arr) {
75 const heap = new MinHeap();
76 for (const x of arr) {
77 heap.push(new HeapItem(x));
78 }
79 const res = [];
80 for (let i = 0; i < 3; i++) {
81 res.push(heap.pop().item);
82 }
83 return res;
84}
85
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
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!