Lecture 1 : Binary Search on 1D Arrays¶
Search target on array using binary search¶
array = {3, 4, 6, 7, 9, 12, 16, 17}
target = 6
result = 2
Approach 1 (Iterative)¶
def binary_search(nums, target):
n = len(nums)
left , right = 0, n-1
while left <= right:
mid = (left + right )//2
if nums[mid] == target:
return mid
elif nums[mid] > target:
right = mid-1
else :
left = mid +1
return -1
print(binary_search([3, 4, 6, 7, 9, 12, 16, 17],6))
2
Approach 2 (Recursive)¶
def binary_search(nums, target):
def helper(left,right):
if left > right:
return -1
mid = (left + right )//2
if nums[mid] == target:
return mid
if nums[mid] > target:
return helper(left,mid-1)
return helper(mid +1 , right)
n = len(nums)
return helper(0,n-1)
print(binary_search([3, 4, 6, 7, 9, 12, 16, 17],6))
2
Complexity¶
- O(log N) : N = len(nums)
- O(1)
Implement Lower Bound¶
Given a sorted array of N integers and an integer x, write a program to find the lower bound of x.
Input Format: N = 4, arr[] = {1,2,2,3}, x = 2
Result: 1
Input Format: N = 5, arr[] = {3,5,8,15,19}, x = 9
Result: 3
Approach 1 (Brute Force)¶
- forward pass
- result = element >= target
def lower_bound(nums,target):
n = len(nums)
for i in range(n):
if nums[i] >= target:
return i
return n
print(lower_bound([1,2,2,3],2))
print(lower_bound([3,5,8,15,19],9))
1 3
Complexity¶
O(N) : N = len(nums)
O(1)
Approach 2 (Binary Search)¶
- even if we find the target , keep moving left
- result = right
def lower_bound(nums,target):
n = len(nums)
left ,right = 0,n-1
result = n
while left <= right:
mid = (left + right) //2
# maybe ans
if nums[mid] >= target:
result = mid
right = mid -1
else:
left = mid +1
return result
print(lower_bound([1,2,2,3],2))
print(lower_bound([3,5,8,15,19],9))
1 3
Complexity¶
O(log N) : N = len(nums)
O(1)
Implement Upper Bound¶
Given a sorted array of N integers and an integer x, write a program to find the upper bound of x.
Input Format: N = 4, arr[] = {1,2,2,3}, x = 2
Result: 3
Input Format: N = 6, arr[] = {3,5,8,9,15,19}, x = 9
Result: 4
Approach 1 (Brute Force)¶
- Forward pass
- result = element > target
def upper_bound(nums,target):
n = len(nums)
for i in range(n):
if nums[i] > target:
return i
return n
print(upper_bound([1,2,2,3],2))
print(upper_bound([3,5,8,9,15,19],9))
3 4
Complexity¶
O(N) : N = len(nums)
O(1)
Approach 2 (Binary Search)¶
- even if u find the same element move right
- result = right
def upper_bound(nums,target):
n = len(nums)
left , right = 0 , n-1
result = n
while left <= right:
mid = (left + right) //2
# maybe ans
if nums[mid]>target:
result = mid
right = mid -1
else:
left = mid+1
return result
print(upper_bound([1,2,2,3],2))
print(upper_bound([3,5,8,9,15,19],9))
3 4
complexity¶
O(log N) : N = len(nums)
O(1)
Search Insert Position¶
You are given a sorted array arr of distinct values and a target value x. You need to search for the index of the target value in the array.
If the value is present in the array, then return its index. Otherwise, determine the index where it would be inserted in the array while maintaining the sorted order.
Input Format: arr[] = {1,2,4,7}, x = 6
Result: 3
Input Format: arr[] = {1,2,4,7}, x = 2
Result: 1
Approach 1 (Brute Force)¶
- use lower bound
- result = element >= target
def insert_pos(nums,target):
n = len(nums)
for i in range(n):
if nums[i] >= target:
return i
return n
print(insert_pos([1,2,4,7],6))
print(insert_pos([1,2,4,7],2))
3 1
Complexity¶
O(N) : N = len(nums)
O(1)
Approach 2 (Binary Search)¶
def insert_pos(nums,target):
n = len(nums)
result = n
left , right = 0,n-1
while left <= right:
mid = (left + right) //2
if nums[mid] >= target:
result = mid
right = mid-1
else:
left = mid+1
return result
print(insert_pos([1,2,4,7],6))
print(insert_pos([1,2,4,7],2))
3 1
Complexity¶
O(log N ) : N = len(nums)
O(1)
Last occurrence in a sorted array¶
Given a sorted array of N integers, write a program to find the index of the last occurrence of the target key. If the target is not found then return -1.
Input: N = 7, target=13, array[] = {3,4,13,13,13,20,40}
Output: 4
Input: N = 7, target=60, array[] = {3,4,13,13,13,20,40}
Output: -1
Approach 1 (brute force)¶
- backward pass
- result = index when element == target
- result = -1 when element < target
def last_occurence(nums,target):
n = len(nums)
for i in range(n-1,-1,-1):
if nums[i] == target:
return i
if nums[i] < target:
return -1
return -1
print(last_occurence([3,4,13,13,13,20,40],13))
print(last_occurence([3,4,13,13,13,20,40],60))
4 -1
complexity¶
O(N) : N = len(nums)
O(1)
Approach2 (Binary Search)¶
- when element = target , search in right
- same as upper bound
def last_occurence(nums,target):
n = len(nums)
result = -1
left , right = 0,n-1
while left <= right:
mid = (left + right ) //2
if nums[mid] == target:
result = mid
left = mid+1
elif nums[mid] < target:
left = mid +1
else:
right = mid -1
return result
print(last_occurence([3,4,13,13,13,20,40],13))
print(last_occurence([3,4,13,13,13,20,40],60))
4 -1
Complexity¶
O(log N) : N = len(nums)
O(1)
First and Last position in sorted array¶
Input: nums = [5,7,7,8,8,10], target = 8
Output: [3,4]
Input: nums = [5,7,7,8,8,10], target = 6
Output: [-1,-1]
Input: nums = [], target = 0
Output: [-1,-1]
Approach 1 (Seprate 2 methods)¶
def both_positions(nums,target):
n = len(nums)
def first_pos():
result = -1
left , right = 0,n-1
while left <= right:
mid = (left + right) //2
if nums[mid] == target:
result = mid
right = mid-1
elif nums[mid] > target:
right = mid -1
else:
left = mid +1
return result
def last_pos():
result = -1
left , right = 0,n-1
while left <= right:
mid = (left + right) //2
if nums[mid] == target:
result = mid
left = mid+1
elif nums[mid] > target:
right = mid -1
else:
left = mid +1
return result
return [first_pos(),last_pos()]
print(both_positions([5,7,7,8,8,10],8))
print(both_positions([5,7,7,8,8,10],6))
print(both_positions([],0))
[3, 4] [-1, -1] [-1, -1]
Approach 2 (Clean 1 method)¶
def both_positions(nums,target):
n = len(nums)
def binary_search(find_first = True):
result = -1
left , right = 0,n-1
while left <= right:
mid = (left + right) //2
if nums[mid] == target:
result = mid
if find_first:
right = mid-1
else:
left = mid +1
elif nums[mid] > target:
right = mid -1
else:
left = mid +1
return result
return [binary_search(True),binary_search(False)]
print(both_positions([5,7,7,8,8,10],8))
print(both_positions([5,7,7,8,8,10],6))
print(both_positions([],0))
[3, 4] [-1, -1] [-1, -1]
complexity¶
O(log N) : N = len(nums)
O(1)
Count Occurrences in Sorted Array¶
Input: N = 7, X = 3 , array[] = {2, 2 , 3 , 3 , 3 , 3 , 4}
Output: 4
Input: N = 8, X = 2 , array[] = {1, 1, 2, 2, 2, 2, 2, 3}
Output: 5
Approach 1 (Brute Force)¶
def count_occurences(nums,target):
count =0
for num in nums:
if num == target:
count +=1
return count
print(count_occurences([2, 2 , 3 , 3 , 3 , 3 , 4],3))
print(count_occurences([1, 1, 2, 2, 2, 2, 2, 3],2))
4 5
Complexity¶
O(N) : N = len(nums)
O(1)
Approach 2 (Binary Search)¶
- find lower and upper index
- result = upper - lower +1
def count_occurences(nums,target):
n = len(nums)
def binary_search(find_first = True):
result = -1
left , right = 0,n-1
while left <= right:
mid = (left + right) //2
if nums[mid] == target:
result = mid
if find_first:
right = mid-1
else:
left = mid +1
elif nums[mid] > target:
right = mid -1
else:
left = mid +1
return result
lower_index ,upper_index = binary_search(True),binary_search(False)
return upper_index - lower_index +1
print(count_occurences([2, 2 , 3 , 3 , 3 , 3 , 4],3))
print(count_occurences([1, 1, 2, 2, 2, 2, 2, 3],2))
4 5
Complexity¶
O(log N) : N = len(nums)
O(1)
Find out how many times the array has been rotated¶
Given an integer array arr of size N, sorted in ascending order (with distinct values). Now the array is rotated between 1 to N times which is unknown. Find how many times the array has been rotated.
Input Format: arr = [4,5,6,7,0,1,2,3]
Result: 4
Input Format: arr = [3,4,5,1,2]
Result: 3
Approach 1 (Brute Force)¶
- Find the smallest number in the array
- result = idx
def count_rotation(nums):
n = len(nums)
small , idx = nums[0] , 0
for i in range(1,n):
if nums[i] < small:
small = nums[i]
idx = i
return idx
print(count_rotation([4,5,6,7,0,1,2,3]))
print(count_rotation([3,4,5,1,2]))
4 3
Complexity¶
O(N) : N = len(nums)
O(1)
Approach 2 (Binary Search)¶
- when nums[mid] > nums[right] , then move towards right and mid cannot be the ans
- otherwise move towards left but mid could be the answer
def count_rotation(nums):
n = len(nums)
result = 0
left,right = 0,n-1
while left <= right:
mid = (left + right) //2
if nums[mid] > nums[right]:
left = mid +1
else:
right =mid -1
result = mid
return result
print(count_rotation([4,5,6,7,0,1,2,3]))
print(count_rotation([3,4,5,1,2]))
4 3
Complexity¶
O(log N ) : N = len(nums)
O(1)
Search Single Element in a sorted array¶
Given an array of N integers. Every number in the array except one appears twice. Find the single number in the array.
Input Format: arr[] = {1,1,2,2,3,3,4,5,5,6,6}
Result: 4
Input Format: arr[] = {1,1,3,5,5}
Result: 3
Approach 1 (Brute Force)¶
- not using that it is sorted
- check each element with every other element
def single_element(nums):
n = len(nums)
for i in range(n):
is_found = False
for j in range(n):
if i != j and nums[i] == nums[j]:
is_found = True
if not is_found:
return nums[i]
return -1
print(single_element([1,1,2,2,3,3,4,5,5,6,6]))
print(single_element([1,1,3,5,5]))
4 3
Complexity¶
O(N ^2) : N = len(nums)
O(1)
Approach 2 (single forward pass )¶
- forward pass with 2 increment
- result = when nums[i] != nums[j] then nums[i]
- we have to handle lots of edge cases
def single_element(nums):
n = len(nums)
if n==1:
return nums[0]
# n is atleast 3
for i in range(0,n-1,2):
if nums[i] != nums[i+1]:
return nums[i]
if nums[n] == nums[n-1]:
return nums[0]
return nums[n-1]
print(single_element([1,1,2,2,3,3,4,5,5,6,6]))
print(single_element([1,1,3,5,5]))
4 3
Complexity¶
O(N)
O(1)
Approach 3(Binary search)¶
- binary search is basically eliminating half where we know result does not exist
- if there are no single numbers pattern : even,odd will always match e.g 0-1 , 2-3 , 4-5
- when mid is odd and matches its left neighbor, we're in the left side and need to search right
- when mid is even and matches its right , we are again in the left side and need to search right
def single_element(nums):
n = len(nums)
# when n=1
if n==1:
return nums[0]
# n >=3
# handling boundary positions
if nums[0] != nums[1]:
return nums[0]
if nums[n-1] != nums[n-2]:
return nums[n-1]
## to prevent out of bounds
left , right = 1, n-1
## binary search
while left <= right:
mid = (left + right) //2
if nums[mid] != nums[mid-1] and nums[mid] != nums[mid +1]:
return nums[mid]
if mid %2 ==0 and nums[mid] == nums[mid+1]:
left = mid +1
elif mid %2 ==1 and nums[mid] == nums[mid-1]:
left = mid +1
else:
right = mid-1
print(single_element([1,1,2,2,3,3,4,5,5,6,6]))
print(single_element([1,1,3,5,5]))
4 3
Complexity¶
O(log N ) : N =len(nums)
O(1)