Find the Smallest Divisor Given a Threshold

Medium
9 days ago

Given an array of integers nums and an integer threshold, we will choose a positive integer divisor, divide all the array by it, and sum the division's result. Find the smallest divisor such that the result mentioned above is less than or equal to threshold.

Each result of the division is rounded to the nearest integer greater than or equal to that element. (For example: 7/3 = 3 and 10/2 = 5).

The test cases are generated so that there will be an answer.

Example 1:

Input: nums = [1,2,5,9], threshold = 6
Output: 5
Explanation: We can get a sum to 17 (1+2+5+9) if the divisor is 1. 
If the divisor is 4 we can get a sum of 7 (1+1+2+3) and if the divisor is 5 the sum will be 5 (1+1+1+2).

Example 2:

Input: nums = [44,22,33,11,1], threshold = 5
Output: 44

Constraints:

  • 1 <= nums.length <= 5 * 10^4
  • 1 <= nums[i] <= 10^6
  • nums.length <= threshold <= 10^6
Sample Answer
def smallestDivisor(nums, threshold):
    def check(divisor):
        total_sum = 0
        for num in nums:
            total_sum += (num + divisor - 1) // divisor  # Equivalent to math.ceil(num / divisor)
        return total_sum <= threshold

    left, right = 1, max(nums)
    ans = right

    while left <= right:
        mid = left + (right - left) // 2
        if check(mid):
            ans = mid
            right = mid - 1
        else:
            left = mid + 1

    return ans

Explanation:

  1. smallestDivisor(nums, threshold) Function:

    • Takes an array of integers nums and an integer threshold as input.
    • Aims to find the smallest positive integer divisor such that the sum of ceil(num / divisor) for each num in nums is less than or equal to threshold.
  2. check(divisor) Helper Function:

    • Takes a divisor as input.
    • Calculates the sum of the ceiling of the division of each number in nums by the divisor.
      • total_sum += (num + divisor - 1) // divisor efficiently computes the ceiling of the division.
    • Returns True if the calculated total_sum is less than or equal to the threshold, and False otherwise.
  3. Binary Search Initialization:

    • left = 1: The smallest possible divisor is 1.
    • right = max(nums): The largest possible divisor we need to consider is the maximum value in the input array.
    • ans = right: Initialize ans to the maximum value as the initial possible answer.
  4. Binary Search Loop:

    • while left <= right
      • mid = left + (right - left) // 2: Calculate the middle value to avoid potential integer overflow.
      • if check(mid): If using mid as the divisor results in a sum less than or equal to the threshold:
        • ans = mid: Update ans to the current mid.
        • right = mid - 1: Try a smaller divisor to find the smallest possible one.
      • else:
        • left = mid + 1: The current divisor mid is too small, so try a larger divisor.
  5. Return Value:

    • The function returns the ans, which represents the smallest divisor that satisfies the given condition.

Example Walkthrough:

Let's trace the execution with nums = [1, 2, 5, 9] and threshold = 6:

  1. left = 1, right = 9, ans = 9
  2. Loop 1: mid = 5
    • check(5): (1+5-1)//5 + (2+5-1)//5 + (5+5-1)//5 + (9+5-1)//5 = 1 + 1 + 1 + 2 = 5 <= 6 (True)
    • ans = 5, right = 4
  3. Loop 2: mid = 2
    • check(2): (1+2-1)//2 + (2+2-1)//2 + (5+2-1)//2 + (9+2-1)//2 = 1 + 2 + 3 + 5 = 11 <= 6 (False)
    • left = 3
  4. Loop 3: mid = 3
    • check(3): (1+3-1)//3 + (2+3-1)//3 + (5+3-1)//3 + (9+3-1)//3 = 1 + 1 + 2 + 4 = 8 <= 6 (False)
    • left = 4
  5. Loop 4: mid = 4
    • check(4): (1+4-1)//4 + (2+4-1)//4 + (5+4-1)//4 + (9+4-1)//4 = 1 + 1 + 2 + 3 = 7 <= 6 (False)
    • left = 5
  6. Loop terminates: left = 5, right = 4, left > right
  7. Return ans = 5

Time and Space Complexity:

  • Time Complexity: O(N log M), where N is the length of the nums array and M is the maximum value in the nums array. The binary search takes O(log M) time, and the check function iterates through the nums array in O(N) time.
  • Space Complexity: O(1), as the algorithm uses only a constant amount of extra space.

Edge Cases:

  • Empty Input: If nums is empty, the code will still work correctly because the sum in the check function will be 0, which will likely be less than or equal to the threshold. It will return 1.
  • Threshold Smaller than the number of elements in nums: In this case, the algorithm will return the largest number in the nums array

Alternative Approach (Brute Force):

A naive approach would be to iterate through possible divisors from 1 to the maximum value in nums and check if the condition is satisfied. This has a time complexity of O(N * M) which is slower than binary search. Here's an example:

def smallestDivisor_brute_force(nums, threshold):
    max_num = max(nums)
    for divisor in range(1, max_num + 1):
        total_sum = 0
        for num in nums:
            total_sum += (num + divisor - 1) // divisor
        if total_sum <= threshold:
            return divisor

The binary search approach is much more efficient, especially when the maximum value in nums is large.