1283. Find the Smallest Divisor Given a Threshold
Problem Description
In this problem, you are given an array of integers nums
and another integer threshold
. Your task is to find the smallest positive integer divisor
such that when each element of the array nums
is divided by this divisor
, the results of the division are rounded up to the nearest integer, and these integers are summed up, the sum is less than or equal to the given threshold
. The division rounding should round up to the nearest integer greater than or equal to the result of the division. For instance, 7 divided by 3 should be rounded up to 3 (since 7/3 = 2.33, and rounding up gives us 3), and similarly, 10 divided by 2 should be rounded to 5. The problem ensures that there will always be an answer.
Intuition
The key observation here is that as the divisor
increases, the resultant sum after the division decreases. This relationship between the divisor
and the sum is monotonous; hence we can employ a binary search strategy to efficiently find the smallest divisor
which results in a sum that is less than or equal to the threshold
.
We can start our binary search with the left boundary (l
) set to 1, since we are looking for positive divisors only, and the right boundary (r
) set to the maximum value in nums
, because dividing by a number larger than the largest number in nums
would result in every division result being 1 (or close to 1), which would be the smallest possible sum. We then repeatedly divide the range by finding the midpoint (mid
) and testing if the sum of the rounded division results is less than or equal to the threshold
. If it is, we know that we can possibly reduce our divisor
even further; thus we adjust our right boundary to mid
. If the sum exceeds threshold
, we need a larger divisor
, so we adjust our left boundary to mid + 1
.
By iteratively applying this binary search, we converge upon the smallest possible divisor
that satisfies the condition. When our search interval is reduced to a single point (l
== r
), we can return l
as our answer, as it represents the smallest divisor for which the sum of the division results does not exceed the threshold
.
Learn more about Binary Search patterns.
Solution Approach
The solution uses a binary search algorithm, which is a classic method to find a target value within a sorted array by repeatedly dividing the search interval in half. This pattern relies on the property that the array is monotonous, meaning the function we are trying to optimize (in this case, the sum of the division results) moves in a single direction as we traverse the array.
Binary Search Implementation
The search starts with setting up two pointers, l
and r
, which represent the boundaries of the potential divisor values. The left boundary l
is initialized to 1 (since we are looking for a positive divisor), while the right boundary r
is set to the maximum value in nums
, as the sum will certainly be less than or equal to the threshold
when divided by the maximum value or any larger number since the division results will be close to 1.
The binary search proceeds by computing the midpoint mid = (l + r) >> 1
(this is equivalent to (l + r) / 2
, but uses bit shifting for potentially faster computation in some languages). For each element x
in nums
, we divide x
by the current mid
and then round up the result to the nearest integer by performing (x + mid - 1) // mid
. We take the sum of all these numbers and compare them to threshold
.
-
If the sum is less than or equal to
threshold
, thenmid
can be a valid candidate for the smallest divisor, but there might be a smaller one. Hence, we move the right boundaryr
tomid
to narrow down the search towards smaller divisors. -
On the contrary, if the sum is greater than
threshold
,mid
is too small of a divisor, resulting in a larger sum. Thus, we move the left boundaryl
tomid + 1
to search among larger divisors.
This search process loops until l
becomes equal to r
, meaning that the space for search has been narrowed down to a single element which is the smallest possible divisor to meet the condition. As soon as this condition is met, the implementation returns l
as the solution.
The patterns and data structures used in this solution are fairly simple—a single array to iterate through the integers of nums
, and integer variables to manage the binary search boundaries and the running sum. This results in an efficient solution that requires only O(log(max(nums)) * len(nums)) operations, as each binary search iteration involves summing len(nums)
elements, and there can be at most log(max(nums))
such iterations.
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 walk through a small example to illustrate the solution approach.
Suppose we have nums = [4, 9, 8]
and threshold = 6
.
Initial Setup
We initialize our binary search bounds: l
starts at 1 because we want a positive divisor, and r
starts at 9, which is the maximum value in nums
, since it is the largest the divisor can be to still produce a sum <= threshold
.
First iteration:
l = 1
,r = 9
mid = (l + r) // 2
. Here,(1 + 9) // 2 = 5
.- We now divide each element of
nums
bymid
and round up:- For
4
:(4 + 5 - 1) // 5
yields1
. - For
9
:(9 + 5 - 1) // 5
yields3
. - For
8
:(8 + 5 - 1) // 5
yields2
.
- For
- The sum of rounded divisions is
1 + 3 + 2 = 6
which is equal tothreshold
. - Hence, we can still try to find a smaller divisor. So we move the right boundary
r
tomid
, sor = 5
.
Second iteration:
l = 1
,r = 5
mid = (l + r) // 2
. Here,(1 + 5) // 2 = 3
.- Divide and round up:
- For
4
:(4 + 3 - 1) // 3
yields2
. - For
9
:(9 + 3 - 1) // 3
yields4
. - For
8
:(8 + 3 - 1) // 3
yields3
.
- For
- The sum is
2 + 4 + 3 = 9
, which is greater thanthreshold
. - Because the sum exceeded the
threshold
, we adjust our left boundary tomid + 1
, which makesl = 4
.
Third iteration:
l = 4
,r = 5
mid = (l + r) // 2
. Here,(4 + 5) // 2 = 4
.- Divide and round up:
- For
4
:(4 + 4 - 1) // 4
yields1
. - For
9
:(9 + 4 - 1) // 4
yields3
. - For
8
:(8 + 4 - 1) // 4
yields3
.
- For
- The sum is
1 + 3 + 3 = 7
, which is greater thanthreshold
. - Therefore,
l = mid + 1
, makingl = 5
.
Fourth iteration:
- At this point,
l = 5
andr = 5
, so we have reached our single point wherel
andr
are equal. - The loop terminates as the space for search has been narrowed down to a single element.
We conclude that the smallest divisor given nums = [4, 9, 8]
and threshold = 6
is 5
since any smaller divisor would result in a sum greater than the threshold, and it is the smallest divisor for which the division results, when summed up, are equal to threshold
.
Solution Implementation
1from typing import List
2
3class Solution:
4 def smallestDivisor(self, nums: List[int], threshold: int) -> int:
5 # Define the search space for the smallest divisor, which starts from 1 to the max of the input list
6 left, right = 1, max(nums)
7
8 # Use binary search to find the smallest divisor within the search space
9 while left < right:
10 # Calculate the middle point of the current search space
11 mid = (left + right) // 2
12 # Calculate the sum using the current divisor (mid)
13 # The sum is of ceil(x / mid) for each x in nums, implemented as (x + mid - 1) // mid to use integer division
14 if sum((num + mid - 1) // mid for num in nums) <= threshold:
15 # If the sum is less than or equal to the threshold, we can try to find a smaller divisor
16 right = mid
17 else:
18 # If the sum is greater than the threshold, we need to increase the divisor
19 left = mid + 1
20 # The left variable will be the smallest divisor at the end of the loop, return it
21 return left
22
1class Solution {
2
3 // Main method to find the smallest divisor given an integer array and a threshold
4 public int smallestDivisor(int[] nums, int threshold) {
5 // Initialize the search range: lower bound 'left' and upper bound 'right'
6 int left = 1, right = 1000000;
7
8 // Perform binary search for the optimal divisor
9 while (left < right) {
10 int mid = (left + right) >> 1; // Equivalent to (left + right) / 2
11
12 // Calculate the sum of the divided numbers which are rounded up
13 int sum = 0;
14 for (int num : nums) {
15 sum += (num + mid - 1) / mid; // `(num + mid - 1) / mid` rounds up
16 }
17
18 // Compare the sum with the threshold to adjust the search range
19 if (sum <= threshold) {
20 // If the sum is less than or equal to threshold, narrow the upper bound
21 right = mid;
22 } else {
23 // If the sum exceeds threshold, narrow the lower bound
24 left = mid + 1;
25 }
26 }
27
28 // The left boundary of the search range is the smallest divisor
29 return left;
30 }
31}
32
1#include <vector>
2#include <algorithm> // Needed for std::max_element
3
4class Solution {
5public:
6 int smallestDivisor(std::vector<int>& nums, int threshold) {
7 // Initialize the lower bound of divisor search range to 1
8 int left = 1;
9 // Initialize the upper bound of divisor search range to the maximum value in the input nums array
10 int right = *std::max_element(nums.begin(), nums.end());
11
12 // Conduct a binary search within the range to find the smallest divisor
13 while (left < right) {
14 int mid = (left + right) >> 1; // This finds the middle point by averaging left and right
15 int sum = 0; // Initialize the sum to store cumulative division results
16
17 // Loop through all numbers in the array and divide them by the current divisor 'mid'
18 for (int num : nums) {
19 // The division operation here takes the ceiling to ensure we don't underestimate
20 sum += (num + mid - 1) / mid;
21 }
22
23 // If the sum is less than or equal to the threshold, we adjust the right bound
24 if (sum <= threshold) {
25 right = mid; // Possible divisor found, we can discard the upper half of the search space
26 } else {
27 left = mid + 1; // Sum too high, increase the divisor by adjusting the lower bound
28 }
29 }
30 // The left bound is now the smallest divisor that meets the condition as while loop exists
31 // when left and right converge
32 return left;
33 }
34};
35
1// This function finds the smallest divisor such that the sum of each number
2// in the array divided by this divisor is less than or equal to the threshold.
3function smallestDivisor(nums: number[], threshold: number): number {
4 // Initialize the left bound of the search space to 1.
5 let left = 1;
6 // Initialize the right bound to the maximum number in the array, as the divisor
7 // can't be larger than the largest number in the array.
8 let right = Math.max(...nums);
9
10 // Perform a binary search to find the smallest divisor.
11 while (left < right) {
12 // Calculate the middle value of the current search space.
13 const mid = Math.floor((left + right) / 2);
14
15 // Initialize the sum of divided elements.
16 let sum = 0;
17 // Calculate the division sum of the array by the current mid value.
18 for (const num of nums) {
19 sum += Math.ceil(num / mid); // Ceil to ensure the division result is an integer.
20 }
21
22 // If the current sum is within the threshold, try to find a smaller divisor,
23 // so narrow the right bound of the search space.
24 if (sum <= threshold) {
25 right = mid;
26 } else {
27 // If the current sum exceeds the threshold, a larger divisor is needed,
28 // so narrow the left bound of the search space.
29 left = mid + 1;
30 }
31 }
32
33 // The left bound becomes the smallest divisor that satisfies the condition at
34 // the end of the loop, as the loop keeps narrowing the search space.
35 return left;
36}
37
Time and Space Complexity
Time Complexity
The given algorithm utilizes a binary search, and within each iteration of this search, it computes the sum of the quotients obtained by dividing each number in the nums
list by the current divisor (mid
). The binary search varies the divisor mid
from 1
to max(nums)
, shrinking this range by half with each iteration until it converges to the smallest possible divisor that satisfies the condition. Since we're halving the range in which we're searching, the binary search has a logarithmic complexity relative to the range of possible divisors. The complexity of the binary search part can be expressed as O(log M)
, where M
is the maximum value in nums
.
For each iteration of binary search, we perform a sum operation which involves iterating over all n
elements in nums
, calculating the quotient for each element, and summing those quotients up. This process has a linear time complexity concerning the number of elements, which is O(n)
.
Combining the two parts, the total computational complexity is the product of the complexity of the binary search and the complexity of the sum operation performed at each step. Therefore, the time complexity of the entire algorithm is O(n log M)
.
Space Complexity
The space complexity of the algorithm refers to the amount of additional memory space that the algorithm requires aside from the input itself. This algorithm uses a constant amount of extra space for variables such as l
, r
, mid
, and the space used for the running total in the sum operation. Since this does not grow with the size of the input list nums
, the space complexity is O(1)
.
Learn more about how to find time and space complexity quickly using problem constraints.
What's the output of running the following function using the following tree as input?
1def serialize(root):
2 res = []
3 def dfs(root):
4 if not root:
5 res.append('x')
6 return
7 res.append(root.val)
8 dfs(root.left)
9 dfs(root.right)
10 dfs(root)
11 return ' '.join(res)
12
1import java.util.StringJoiner;
2
3public static String serialize(Node root) {
4 StringJoiner res = new StringJoiner(" ");
5 serializeDFS(root, res);
6 return res.toString();
7}
8
9private static void serializeDFS(Node root, StringJoiner result) {
10 if (root == null) {
11 result.add("x");
12 return;
13 }
14 result.add(Integer.toString(root.val));
15 serializeDFS(root.left, result);
16 serializeDFS(root.right, result);
17}
18
1function serialize(root) {
2 let res = [];
3 serialize_dfs(root, res);
4 return res.join(" ");
5}
6
7function serialize_dfs(root, res) {
8 if (!root) {
9 res.push("x");
10 return;
11 }
12 res.push(root.val);
13 serialize_dfs(root.left, res);
14 serialize_dfs(root.right, res);
15}
16
Recommended Readings
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
LeetCode Patterns Your Personal Dijkstra's Algorithm to Landing Your Dream Job The goal of AlgoMonster is to help you get a job in the shortest amount of time possible in a data driven way We compiled datasets of tech interview problems and broke them down by patterns This way we
Recursion Recursion is one of the most important concepts in computer science Simply speaking recursion is the process of a function calling itself Using a real life analogy imagine a scenario where you invite your friends to lunch https algomonster s3 us east 2 amazonaws com recursion jpg You first
Want a Structured Path to Master System Design Too? Don’t Miss This!