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
- math.ceil(a/b) = a+b-1 //b
In [ ]:
def smallest_divisor(nums,target):
max_element = max(nums)
for i in range(1,max_element +1,1):
sum =0
for num in nums:
sum += (num + i-1)//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 [ ]:
def smallest_divisor(nums,target):
def compute_sum(val):
sum =0
for num in nums:
sum += (num + val -1 ) // val
return sum
max_element = max(nums)
left , right = 1,max_element
result =-1
while left <= right:
mid = (left + right) //2
if compute_sum(mid) <= target:
result = mid
right = mid -1
else:
left = 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)
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 (Linear Search without arrray)¶
In [12]:
def missing_num(nums,k):
def linear_search(target):
for num in nums:
if num == target:
return True
return False
max_val = max(nums)
count =0
for i in range(1,max_val):
if not linear_search(i):
count +=1
if count ==k:
return i
return -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(1)
Approach 3 (Replace Linear Search with set)¶
In [ ]:
def missing_num(nums,k):
nums_set = set(nums)
max_val = max(nums)
count =0
for i in range(1,max_val):
if i not in nums_set:
count +=1
if count ==k:
return i
return -1
print(missing_num([4,7,9,10],1))
print(missing_num([4,7,9,10],4))
1 5
Complexity¶
- MAX_VAL = max val in nums , N = len(nums)
- O(MAX_VAL)
- O(N)
Approach 4 (Replace Linear Search with binary search) Without any¶
- 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
- After binary search, left is at the position where k-th missing number exists
- result = left + k (treat left as the offset)
In [35]:
def missing_num(nums, k):
n = len(nums)
left, right = 0, n - 1
while left <= right:
mid = (left + right) // 2
missing_count = nums[mid] - (mid + 1)
if missing_count < k:
left = mid + 1
else:
right = mid - 1
return left + k
print(missing_num([4, 7, 9, 10], 1)) # Should output: 1
print(missing_num([4, 7, 9, 10], 4)) # Should output: 5
1 5
Complexity¶
O(log N) : N = len(nums)
O(1)