Facebook Pixel

1283. Find the Smallest Divisor Given a Threshold

Problem Description

You are given an array of integers nums and an integer threshold. Your task is to find the smallest positive integer divisor such that when you divide all elements in the array by this divisor and sum up the results, the total sum is less than or equal to the threshold.

When dividing each element, the result must be rounded up to the nearest integer (ceiling function). For example:

  • 7/3 = 3 (since 2.33... rounds up to 3)
  • 10/2 = 5 (exactly 5, no rounding needed)

The problem guarantees that there will always be a valid answer.

Example walkthrough: If nums = [1, 2, 5, 9] and threshold = 6:

  • If divisor = 5: ceil(1/5) + ceil(2/5) + ceil(5/5) + ceil(9/5) = 1 + 1 + 1 + 2 = 5 ≤ 6
  • If divisor = 4: ceil(1/4) + ceil(2/4) + ceil(5/4) + ceil(9/4) = 1 + 1 + 2 + 3 = 7 > 6

The goal is to find the smallest divisor value that keeps the sum within the threshold limit.

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

Intuition

The key observation is that as the divisor increases, the sum of divided results decreases. This creates a monotonic relationship:

  • Small divisor → Large sum (each division gives a larger result)
  • Large divisor → Small sum (each division gives a smaller result)

For example, with nums = [10]:

  • divisor = 1: ceil(10/1) = 10
  • divisor = 2: ceil(10/2) = 5
  • divisor = 5: ceil(10/5) = 2
  • divisor = 10: ceil(10/10) = 1

Since we want the smallest divisor where the sum ≤ threshold, we're looking for a turning point: the first value where the condition becomes true. Everything to the left fails (sum too large), and everything to the right succeeds (sum small enough).

This monotonic property screams binary search! We can search for the smallest valid divisor in the range [1, max(nums)]. Why max(nums) as the upper bound? Because dividing any number by a value greater than or equal to itself always yields 1 (after ceiling), so using any divisor larger than max(nums) won't reduce the sum further.

The binary search checks each candidate divisor by calculating the sum. If the sum is within the threshold, we know this divisor works, but there might be a smaller one, so we search the left half. If the sum exceeds the threshold, we need a larger divisor to reduce the sum, so we search the right half.

Learn more about Binary Search patterns.

Solution Approach

The solution uses binary search to find the smallest valid divisor. Here's how the implementation works:

Binary Search Setup:

  • Left boundary: l = 1 (smallest possible divisor)
  • Right boundary: r = max(nums) (any divisor larger than this won't reduce the sum further)

The Search Process:

  1. Calculate the middle point: mid = (l + r) // 2
  2. For the current mid, calculate the sum of all divisions: s = sum(ceil(x/mid) for x in nums)
  3. Compare the sum with threshold:
    • If s ≤ threshold: This divisor works, but there might be a smaller one. Update r = mid
    • If s > threshold: We need a larger divisor to reduce the sum. Update l = mid + 1
  4. Repeat until l meets r

Calculating Ceiling Division: The code uses a clever trick to calculate ceil(x/v) without using the ceiling function:

  • Formula: (x + v - 1) // v
  • This works because adding v - 1 to the numerator ensures rounding up for any remainder

The Given Solution's Approach: The provided code uses Python's bisect_left with a custom key function:

def f(v: int) -> bool:
    v += 1
    return sum((x + v - 1) // v for x in nums) <= threshold

return bisect_left(range(max(nums)), True, key=f) + 1

This function f(v) returns True if divisor v+1 produces a sum ≤ threshold. The bisect_left finds the leftmost position where True would fit in the boolean sequence, which corresponds to our smallest valid divisor. The +1 adjustments handle the 0-indexed nature of the range.

Time Complexity: O(n * log(max(nums))) where n is the length of the array Space Complexity: O(1) excluding the input array

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 a small example to illustrate the binary search approach.

Given: nums = [2, 7, 11] and threshold = 10

Goal: Find the smallest divisor such that ceil(2/d) + ceil(7/d) + ceil(11/d) ≤ 10

Step 1: Initialize binary search boundaries

  • Left boundary: l = 1
  • Right boundary: r = max(nums) = 11
  • Search range: [1, 11]

Step 2: First iteration

  • mid = (1 + 11) // 2 = 6
  • Calculate sum with divisor 6:
    • ceil(2/6) + ceil(7/6) + ceil(11/6) = 1 + 2 + 2 = 5
  • Since 5 ≤ 10, divisor 6 works! But can we find a smaller one?
  • Update: r = 6, new range: [1, 6]

Step 3: Second iteration

  • mid = (1 + 6) // 2 = 3
  • Calculate sum with divisor 3:
    • ceil(2/3) + ceil(7/3) + ceil(11/3) = 1 + 3 + 4 = 8
  • Since 8 ≤ 10, divisor 3 works! Continue searching for smaller.
  • Update: r = 3, new range: [1, 3]

Step 4: Third iteration

  • mid = (1 + 3) // 2 = 2
  • Calculate sum with divisor 2:
    • ceil(2/2) + ceil(7/2) + ceil(11/2) = 1 + 4 + 6 = 11
  • Since 11 > 10, divisor 2 is too small (sum exceeds threshold).
  • Update: l = 3, new range: [3, 3]

Step 5: Convergence

  • l = r = 3, so we've found our answer!

Answer: The smallest divisor is 3, which gives us a sum of 8 ≤ 10.

Verification of neighboring values:

  • Divisor 2: sum = 11 (too large, exceeds threshold)
  • Divisor 3: sum = 8 (valid, within threshold) ✓
  • Divisor 4: sum = 6 (valid, but not the smallest)

The binary search efficiently found the turning point where the divisor becomes just large enough to keep the sum within the threshold.

Solution Implementation

1class Solution:
2    def smallestDivisor(self, nums: List[int], threshold: int) -> int:
3        def can_meet_threshold(divisor: int) -> bool:
4            """
5            Check if using (divisor + 1) as the actual divisor keeps sum <= threshold.
6          
7            The bisect_left searches in range [0, max(nums)-1], so we need to add 1
8            to get the actual divisor value for calculation.
9          
10            Args:
11                divisor: The index value from bisect_left (0-indexed)
12          
13            Returns:
14                True if the sum of divisions is <= threshold, False otherwise
15            """
16            actual_divisor = divisor + 1
17            # Calculate sum of ceiling divisions: ceil(x/d) = (x + d - 1) // d
18            total_sum = sum((num + actual_divisor - 1) // actual_divisor for num in nums)
19            return total_sum <= threshold
20      
21        # Binary search for the smallest divisor that meets the threshold
22        # bisect_left finds the leftmost position where can_meet_threshold returns True
23        # Search range is [0, max(nums)-1], representing divisors [1, max(nums)]
24        smallest_index = bisect_left(range(max(nums)), True, key=can_meet_threshold)
25      
26        # Add 1 to convert from 0-indexed position to actual divisor value
27        return smallest_index + 1
28
1class Solution {
2    public int smallestDivisor(int[] nums, int threshold) {
3        // Binary search range: minimum divisor is 1, maximum is 1000000
4        int left = 1;
5        int right = 1000000;
6      
7        // Binary search to find the smallest valid divisor
8        while (left < right) {
9            // Calculate middle value
10            int mid = (left + right) >> 1;  // Equivalent to (left + right) / 2
11          
12            // Calculate sum of all divisions with current divisor
13            int sum = 0;
14            for (int num : nums) {
15                // Ceiling division: (num + mid - 1) / mid equals Math.ceil(num / mid)
16                sum += (num + mid - 1) / mid;
17            }
18          
19            // Check if current divisor satisfies the threshold
20            if (sum <= threshold) {
21                // Current divisor works, try to find smaller one
22                right = mid;
23            } else {
24                // Current divisor too small, need larger divisor
25                left = mid + 1;
26            }
27        }
28      
29        // Return the smallest divisor that satisfies the condition
30        return left;
31    }
32}
33
1class Solution {
2public:
3    int smallestDivisor(vector<int>& nums, int threshold) {
4        // Initialize binary search bounds
5        // Left bound: minimum possible divisor is 1
6        int left = 1;
7        // Right bound: maximum possible divisor is the largest element in nums
8        // (dividing by larger values would always give sum = nums.size())
9        int right = *max_element(nums.begin(), nums.end());
10      
11        // Binary search for the smallest valid divisor
12        while (left < right) {
13            // Calculate middle point
14            int mid = (left + right) >> 1;  // Equivalent to (left + right) / 2
15          
16            // Calculate sum of divisions with current divisor
17            int divisionSum = 0;
18            for (int num : nums) {
19                // Using ceiling division: (num + mid - 1) / mid
20                // This ensures we round up when there's a remainder
21                divisionSum += (num + mid - 1) / mid;
22            }
23          
24            // Check if current divisor satisfies the threshold
25            if (divisionSum <= threshold) {
26                // Current divisor works, try to find a smaller one
27                right = mid;
28            } else {
29                // Current divisor is too small, need a larger one
30                left = mid + 1;
31            }
32        }
33      
34        // Return the smallest divisor that satisfies the threshold
35        return left;
36    }
37};
38
1/**
2 * Finds the smallest divisor such that the sum of division results is less than or equal to threshold
3 * @param nums - Array of positive integers to divide
4 * @param threshold - Maximum allowed sum after division
5 * @returns The smallest positive integer divisor
6 */
7function smallestDivisor(nums: number[], threshold: number): number {
8    // Initialize binary search boundaries
9    // Minimum possible divisor is 1
10    let left: number = 1;
11    // Maximum possible divisor is the largest number in the array
12    // (dividing by a number larger than max(nums) will always give sum = nums.length)
13    let right: number = Math.max(...nums);
14  
15    // Binary search for the smallest valid divisor
16    while (left < right) {
17        // Calculate middle point (using bit shift for integer division)
18        const middle: number = (left + right) >> 1;
19      
20        // Calculate the sum of all elements divided by current divisor (rounded up)
21        const divisionSum: number = nums.reduce(
22            (accumulator: number, currentValue: number) => 
23                accumulator + Math.ceil(currentValue / middle), 
24            0
25        );
26      
27        // Check if current divisor satisfies the threshold condition
28        if (divisionSum <= threshold) {
29            // Current divisor works, try to find a smaller one
30            right = middle;
31        } else {
32            // Current divisor is too small, need a larger one
33            left = middle + 1;
34        }
35    }
36  
37    // Return the smallest divisor that satisfies the condition
38    return left;
39}
40

Time and Space Complexity

The time complexity is O(n × log M), where n is the length of the array nums and M is the maximum value in the array nums. This is because:

  • The binary search using bisect_left performs O(log M) iterations over the range [0, max(nums)).
  • In each iteration, the function f is called, which computes the sum of ceiling divisions for all elements in nums, taking O(n) time.
  • Therefore, the total time complexity is O(n × log M).

The space complexity is O(1). The algorithm only uses a constant amount of extra space for variables like v and the sum calculation, regardless of the input size. The range(max(nums)) in bisect_left is treated as a virtual range and doesn't create an actual list in memory.

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

Common Pitfalls

1. Off-by-One Error with Index vs. Divisor Value

The most common mistake is confusion between the index used in bisect_left and the actual divisor value. The code searches through range(max(nums)) which gives indices [0, 1, 2, ..., max(nums)-1], but the actual divisors we want to test are [1, 2, 3, ..., max(nums)].

Wrong approach:

def can_meet_threshold(divisor: int) -> bool:
    # ERROR: Using divisor directly without adding 1
    total_sum = sum((num + divisor - 1) // divisor for num in nums)
    return total_sum <= threshold

# This would cause division by zero when divisor=0

Correct approach:

def can_meet_threshold(divisor: int) -> bool:
    actual_divisor = divisor + 1  # Convert index to actual divisor
    total_sum = sum((num + actual_divisor - 1) // actual_divisor for num in nums)
    return total_sum <= threshold

2. Incorrect Ceiling Division Formula

Many developers try to use floating-point division with math.ceil, which can lead to precision issues and is less efficient.

Problematic approach:

import math
total_sum = sum(math.ceil(num / divisor) for num in nums)
# Can have floating-point precision issues

Better approach:

# Integer-only arithmetic: ceil(x/d) = (x + d - 1) // d
total_sum = sum((num + divisor - 1) // divisor for num in nums)

3. Wrong Binary Search Boundaries

Setting the right boundary incorrectly can miss valid solutions or cause unnecessary iterations.

Wrong:

# Setting right boundary too small
bisect_left(range(min(nums)), True, key=can_meet_threshold)
# May not find valid divisors for larger numbers

# Setting right boundary unnecessarily large
bisect_left(range(sum(nums)), True, key=can_meet_threshold)
# Wastes time checking divisors that can't improve the answer

Correct:

# Right boundary should be max(nums) since any divisor larger 
# than the maximum element won't reduce the sum further
bisect_left(range(max(nums)), True, key=can_meet_threshold)

4. Misunderstanding bisect_left Return Value

bisect_left returns the leftmost position where the condition becomes True. Forgetting to add 1 at the end gives an off-by-one error.

Wrong:

return bisect_left(range(max(nums)), True, key=can_meet_threshold)
# Returns index, not the actual divisor value

Correct:

return bisect_left(range(max(nums)), True, key=can_meet_threshold) + 1
# Converts 0-indexed position to 1-indexed divisor

Alternative Clear Solution

To avoid these pitfalls, consider a more explicit binary search implementation:

def smallestDivisor(self, nums: List[int], threshold: int) -> int:
    left, right = 1, max(nums)
  
    while left < right:
        mid = (left + right) // 2
        total = sum((num + mid - 1) // mid for num in nums)
      
        if total <= threshold:
            right = mid  # Try smaller divisor
        else:
            left = mid + 1  # Need larger divisor
  
    return left

This approach directly works with divisor values rather than indices, making the logic clearer and reducing the chance of off-by-one errors.

Discover Your Strengths and Weaknesses: Take Our 5-Minute Quiz to Tailor Your Study Plan:

Which data structure is used to implement priority queue?


Recommended Readings

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

Load More