Taro Logo

Find the Smallest Divisor Given a Threshold

Medium
a month 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.

For example: 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).

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

Sample Answer
def smallestDivisor(nums, threshold):
    def check(divisor):
        total = 0
        for num in nums:
            total += (num + divisor - 1) // divisor
        return total <= threshold

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

# Example Usage
nums1 = [1, 2, 5, 9]
threshold1 = 6
print(f"Smallest divisor for {nums1} and threshold {threshold1}: {smallestDivisor(nums1, threshold1)}")

nums2 = [44, 22, 33, 11, 1]
threshold2 = 5
print(f"Smallest divisor for {nums2} and threshold {threshold2}: {smallestDivisor(nums2, threshold2)}")

Explanation:

The problem requires finding the smallest divisor such that the sum of the ceiling of each number in the input array divided by the divisor is less than or equal to the given threshold. A binary search approach can efficiently solve this problem.

  1. smallestDivisor(nums, threshold) Function: This function takes the array nums and the integer threshold as input.
  2. check(divisor) Helper Function: This function calculates the sum of the ceiling of each number in nums divided by the given divisor and checks if this sum is less than or equal to threshold.
  3. Binary Search: The main part of the algorithm uses binary search to find the smallest divisor that satisfies the condition. The left boundary is initialized to 1, and the right boundary is initialized to the maximum value in nums.
  4. Update Boundaries: During each iteration of the binary search, the middle value mid is used as a potential divisor. If the check(mid) function returns True, it means the current divisor satisfies the condition, so the right boundary is updated to mid - 1 to search for a smaller divisor. Otherwise, the left boundary is updated to mid + 1 to search for a larger divisor.

Brute Force Solution (Naive)

A straightforward, but less efficient, solution would be to iterate through each possible divisor from 1 to the maximum value in nums and check if it meets the condition. This approach has a time complexity of O(N*M) where N is the number of elements in nums and M is the maximum value in nums.

def smallestDivisor_brute_force(nums, threshold):
    max_num = max(nums)
    for divisor in range(1, max_num + 1):
        total = 0
        for num in nums:
            total += (num + divisor - 1) // divisor
        if total <= threshold:
            return divisor
    return max_num # should never reach here because problem guarantees a solution

nums1 = [1, 2, 5, 9]
threshold1 = 6
print(f"Brute force: Smallest divisor for {nums1} and threshold {threshold1}: {smallestDivisor_brute_force(nums1, threshold1)}")

nums2 = [44, 22, 33, 11, 1]
threshold2 = 5
print(f"Brute force: Smallest divisor for {nums2} and threshold {threshold2}: {smallestDivisor_brute_force(nums2, threshold2)}")

Big(O) Run-time Analysis

The run-time complexity of the optimal solution (binary search) is O(N * log(M)), where N is the number of elements in nums and M is the maximum value in nums.

  • The binary search reduces the search space by half in each iteration, resulting in a logarithmic time complexity of O(log(M)).
  • The check function iterates through each element in nums, resulting in a linear time complexity of O(N).
  • Therefore, the overall run-time complexity is O(N * log(M)).

The brute force solution has a time complexity of O(N*M) where N is the number of elements in nums and M is the maximum value in nums.

Big(O) Space Usage Analysis

The space complexity of the optimal solution is O(1) because it uses a constant amount of extra space, regardless of the size of the input.

Edge Cases and How to Handle Them

  1. Empty Input Array: If the input array nums is empty, the problem statement guarantees there will be at least one number. So, the array can't be empty.
  2. Threshold Too Small: The problem statement specifies nums.length <= threshold. So the threshold is guaranteed to be large enough to ensure a solution exists.
  3. Large Input Values: The input values for nums[i] can be up to 106, so the binary search's right boundary should be initialized accordingly. The data type for the sum in check() should also be able to handle this potential magnitude (but Python ints handle arbitrarily large values).