Lecture -2 Binary Search on answers¶
Find the Smallest Divisor Given a Threshold¶
You are given an array of integers 'arr' and an integer i.e. a threshold value 'limit'. Your task is to find the smallest positive integer divisor, such that upon dividing all the elements of the given array by it, the sum of the division's result is less than or equal to the given threshold value.
Input Format: N = 5, arr[] = {1,2,3,4,5}, limit = 8
Result: 3
Explanation: We can get a sum of 15(1 + 2 + 3 + 4 + 5) if we choose 1 as a divisor.
The sum is 9(1 + 1 + 2 + 2 + 3) if we choose 2 as a divisor. Upon dividing all the elements of the array by 3, we get 1,1,1,2,2 respectively. Now, their sum is equal to 7 <= 8 i.e. the threshold value. So, 3 is the minimum possible answer.
Input Format: N = 4, arr[] = {8,4,2,3}, limit = 10
Result: 2
Explanation: If we choose 1, we get 17 as the sum. If we choose 2, we get 9(4+2+1+2) <= 10 as the answer. So, 2 is the answer.
Point to remember:
While dividing the array elements with a chosen number, we will always take the ceiling value. And then we will consider their summation. For example, 3 / 2 = 2.
Observation:
Minimum possible divisor: We can easily consider 1 as the minimum divisor as it is the smallest positive integer.
Maximum possible divisor: If we observe, we can conclude the maximum element in the array i.e. max(arr[]) is the maximum possible divisor. Any number > max(arr[]), will give the exact same result as max(arr[]) does. This divisor will generate the minimum possible result i.e. n(1 for each element), where n = size of the array.
With these observations, we can surely say that our answer will lie in the range
[1, max(arr[])].
Approach 1 (brute force)¶
- find the max element in the array
- run loop from 1 to max value
- calculate sum = threshold , divisor = ans
In [5]:
import math
def smallest_divisor(nums,target):
max_element = nums[0]
for num in nums:
max_element = max(num,max_element)
for i in range(1,max_element +1,1):
sum =0
for num in nums:
sum +=int(math.ceil(num/i))
if sum <= target:
return i
return -1
print(smallest_divisor([1,2,3,4,5],8))
print(smallest_divisor([8,4,2,3],10))
3 2
Complexity¶
O(MAX_ELEMENT xN) : N = len(nums) and MAX_ELEMENT = maximum value in the nums
O(1)
Approach 2 (Binary Search)¶
- Since the nums are not sorted , we cannot apply binary search
- But divisors are sorted , we will apply binary search
- if sum(mid) > target => search in right
- if sum(mid) <= target => search in left where mid could be the ans
In [6]:
import math
def smallest_divisor(nums,target):
max_element = nums[0]
for num in nums:
max_element = max(num,max_element)
def get_sum(divisor):
sum =0
for num in nums:
sum += math.ceil(num /divisor)
return sum
left ,right = 1,max_element
result = 1
while left <= right:
mid = (left + right) //2
if get_sum(mid) > target:
left= mid +1
else:
result = mid
right = mid -1
return result
print(smallest_divisor([1,2,3,4,5],8))
print(smallest_divisor([8,4,2,3],10))
3 2
Complexity¶
O(log MAX_ELEMENT * N) : MAX_ELEMENT = max(nums) and N = len(nums)
O(1)
Approach 3 (Use built in methods)¶
- to get the maximum val : max_element = max(nums)
- instead of math.ceil(nums/divisor) which is floating division => nums + divisor -1 // divisor
In [8]:
def smallest_divisor(nums,target):
max_element = max(nums)
def get_sum(divisor):
sum =0
for num in nums:
sum += (num + divisor -1) // divisor
return sum
left ,right = 1,max_element
result = 1
while left <= right:
mid = (left + right) //2
if get_sum(mid) > target:
left= mid +1
else:
result = mid
right = mid -1
return result
print(smallest_divisor([1,2,3,4,5],8))
print(smallest_divisor([8,4,2,3],10))
3 2
Kth Missing Positive Number¶
You are given a strictly increasing array ‘vec’ and a positive integer 'k'. Find the 'kth' positive integer missing from 'vec'.
Input Format: vec[]={4,7,9,10}, k = 1
Result: 1
Explanation: The missing numbers are 1, 2, 3, 5, 6, 8, 11, 12, ……, and so on. Since 'k' is 1, the first missing element is 1.
Input Format: vec[]={4,7,9,10}, k = 4
Result: 5
Explanation: The missing numbers are 1, 2, 3, 5, 6, 8, 11, 12, ……, and so on. Since 'k' is 4, the fourth missing element is 5.
Approach 1 (Brute Force)¶
- create a array to store all the missing numbers
- return missing_nums[k-1]
In [10]:
def missing_num(nums,k):
missing = []
n = len(nums)
max_num = nums[n-1]
def linear_search(target):
for num in nums:
if num == target:
return True
return False
for i in range(1,max_num):
if not linear_search(i):
missing.append(i)
return missing[k-1]
print(missing_num([4,7,9,10],1))
print(missing_num([4,7,9,10],4))
1 5
Complexity¶
O(MAX_NUM * N) :MAX_NUM = max(nums) and N = len(nums)
O(MAX_NUM)
Approach 2 (Replace Linear Search with binary search)¶
In [11]:
def missing_num(nums,k):
missing = []
n = len(nums)
max_num = nums[n-1]
def binary_search(target):
left ,right = 0,n-1
while left <= right:
mid = (left + right) //2
if nums[mid] == target:
return True
if nums[mid] > target:
right = mid -1
else:
left = mid +1
return False
for i in range(1,max_num):
if not binary_search(i):
missing.append(i)
return missing[k-1]
print(missing_num([4,7,9,10],1))
print(missing_num([4,7,9,10],4))
1 5
Complexity¶
O(MAX_NUM x log N) : MAX_NUM = max(nums) and N = len(nums)
O(MAX_NUM)
Approach 3 (BS on answers)¶
- again we need to eliminate half on every iteration
- we can easily find the missing count where
- if it is a complete array , nums[mid] = mid +1
- so missing count = nums[mid] - (mid +1)
- no if missing count < k => we need to search in right
- else we need to search in left
- at the end , when left crosses right , left points to the kth position
- result = left + k
In [ ]:
def missing_num(nums,k):
n = len(nums)
left ,right = 0,n-1
result =0
while left <= right:
mid = (left + right) //2
missing_count = nums[mid] - (mid +1)
if missing_count < k:
left = mid +1
result = mid +k
else:
right = mid -1
result = mid +k
return result
print(missing_num([4,7,9,10],1))
print(missing_num([4,7,9,10],4))
1 5
Complexity¶
O(log N) : N = len(nums)
O(1)