Lecture 2 (Medium Problems)¶
Kth largest element in an array¶
Given an unsorted array, print Kth Largest and Smallest Element from an unsorted array.
Input: Array = [1,2,6,4,5,3] , K = 3
Output: kth largest element = 4
Input: Array = [1,2,6,4,5] , k = 3
Output : kth largest element = 4
Approach 1 (Via sorting)¶
def kth_largest_element(nums,k):
n = len(nums)
nums.sort()
return nums[n-k]
print(kth_largest_element([1,2,6,4,5,3],3))
print(kth_largest_element([1,2,6,4,5],3))
4 4
Complexity¶
- N = len(nums)
O(N log N)
O(1)
Approach 2 ( Quick Select)¶
- result = nums[partiion_idx] when parition_idx = n-k
def kth_largest_element(nums,k):
n = len(nums)
def swap(i,j):
nums[i],nums[j] = nums[j],nums[i]
def parition(start, end):
idx = start
left , right = start +1 , end
while left <= right:
if nums[left] <= nums[idx]:
left +=1
elif nums[right] >= nums[idx]:
right -=1
else:
swap(left,right)
left +=1
right -=1
swap(right,idx)
return right
def quick_select(start,end,k):
idx = parition(start,end)
if idx ==k:
return nums[k]
elif idx >k:
return quick_select(start,idx-1,k)
else:
return quick_select(idx +1,end,k)
return quick_select(0,n-1,n-k)
print(kth_largest_element([1,2,6,4,5,3],3))
print(kth_largest_element([1,2,6,4,5],3))
4 4
Complexity¶
- N = len(nums)
- O(N) : Average Case but worst case O(N^2) which can be fixed by randomizing pivot value
- O(1)
Approach 3 (Using heaps)¶
- for finding the kth large element use min heap
import heapq
def kth_largest_element(nums,k):
min_heap = []
for num in nums:
heapq.heappush(min_heap,num)
if len(min_heap) >k:
heapq.heappop(min_heap)
return min_heap[0]
print(kth_largest_element([1,2,6,4,5,3],3))
print(kth_largest_element([1,2,6,4,5],3))
(3, 4) (4, 4)
Complexity¶
- N = len(nums)
- O(N * log K)
- O (K)
Kth smallest element in an array¶
Input: Array = [1,2,6,4,5,3] , K = 3
Output: kth smallest element = 3
Input: Array = [1,2,6,4,5] , k = 3
Output : kth smallest element = 4
Approach 1 (Sorting)¶
- result = sorted(nums)[i-1]
def kth_smallest_element(nums,k):
return sorted(nums)[k-1]
print(kth_smallest_element([1,2,6,4,5,3],3))
print(kth_smallest_element([1,2,6,4,5],3))
3 4
Complexity¶
N = len(nums)
O(N log N)
O(1)
Approach 2 (Quick Select)¶
def kth_smallest_element(nums,k):
def swap(i,j):
nums[i],nums[j] = nums[j],nums[i]
def partition(start,end):
pivot = start
left , right = start +1,end
while left <= right:
if nums[left] <= nums[pivot]:
left +=1
elif nums[right] >= nums[pivot]:
right -=1
else:
swap(left,right)
left +=1
right -=1
swap(right,pivot)
return right
def quick_select(start,end):
pivot = partition(start,end)
if pivot == (k-1):
return nums[pivot]
if pivot > (k-1):
return quick_select(start,pivot-1)
return quick_select(pivot+1,end)
return quick_select(0,len(nums)-1)
print(kth_smallest_element([1,2,6,4,5,3],3))
print(kth_smallest_element([1,2,6,4,5],3))
3 4
Complexity¶
N = len(nums)
O(N^2) but average = O(N)
O(log N) : recursion
Approach 3 (Quick Select Without Recursion)¶
def kth_smallest_element(nums,k):
def swap(i,j):
nums[i],nums[j] = nums[j],nums[i]
def partition(start,end):
pivot = start
left , right = start +1,end
while left <= right:
if nums[left] <= nums[pivot]:
left +=1
elif nums[right] >= nums[pivot]:
right -=1
else:
swap(left,right)
left +=1
right -=1
swap(right,pivot)
return right
def quick_select(start,end):
while start <= end:
pivot = partition(start,end)
if pivot == (k-1):
return nums[pivot]
elif pivot > (k-1):
end = pivot -1
else:
start =pivot +1
return quick_select(0,len(nums)-1)
print(kth_smallest_element([1,2,6,4,5,3],3))
print(kth_smallest_element([1,2,6,4,5],3))
3 4
Complexity¶
N = len(nums)
O(N^2) average O(N)
O(1)
Approach 4 (Random partiion)¶
import random
def kth_smallest_element(nums,k):
def swap(i,j):
nums[i],nums[j] = nums[j],nums[i]
def partition(start,end):
random_idx = random.randint(start,end)
swap(random_idx,start)
pivot = start
left , right = start +1,end
while left <= right:
if nums[left] <= nums[pivot]:
left +=1
elif nums[right] >= nums[pivot]:
right -=1
else:
swap(left,right)
left +=1
right -=1
swap(right,pivot)
return right
def quick_select(start,end):
while start <= end:
pivot = partition(start,end)
if pivot == (k-1):
return nums[pivot]
elif pivot > (k-1):
end = pivot -1
else:
start =pivot +1
return quick_select(0,len(nums)-1)
print(kth_smallest_element([1,2,6,4,5,3],3))
print(kth_smallest_element([1,2,6,4,5],3))
3 4
Time Complexity¶
N = len(nums)
O(N)
O(1)
Approach 5 (Min Heap)¶
import heapq
def kth_smallest_element(nums,k):
max_heap = []
for num in nums:
heapq.heappush(max_heap,-num)
if len(max_heap) > k:
heapq.heappop(max_heap)
return -1*heapq.heappop(max_heap)
print(kth_smallest_element([1,2,6,4,5,3],3))
print(kth_smallest_element([1,2,6,4,5],3))
3 4
Complexity¶
N = len(nums)
O(N log K)
O(K)
Sort K sorted array¶
Given an array arr[] and a number k . The array is sorted in a way that every element is at max k distance away from it sorted position. It means if we completely sort the array, then the index of the element can go from i - k to i + k where i is index in the given array. Our task is to completely sort the array.
arr = [6, 5, 3, 2, 8, 10, 9], k = 3
[2, 3, 5, 6, 8, 9, 10]
Input : arr = [1, 4, 5, 2, 3, 6, 7, 8, 9, 10], k = 2
Output : [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
Apporach 1 (Brute Force)¶
def k_sorted(nums,k):
return sorted(nums)
print(k_sorted([6, 5, 3, 2, 8, 10, 9],3))
print(k_sorted([1, 4, 5, 2, 3, 6, 7, 8, 9, 10],2))
[2, 3, 5, 6, 8, 9, 10] [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
Complexity¶
O(N log N)
O(1)
Approach 2 (Min heapa of size k)¶
import heapq
def k_sorted(nums,k):
min_heap = []
for i in range(k+1):
heapq.heappush(min_heap,nums[i])
n = len(nums)
result = []
for i in range(k+1,n):
result.append(heapq.heappop(min_heap))
heapq.heappush(min_heap,nums[i])
while len(min_heap)>0:
result.append(heapq.heappop(min_heap))
return result
print(k_sorted([6, 5, 3, 2, 8, 10, 9],3))
print(k_sorted([1, 4, 5, 2, 3, 6, 7, 8, 9, 10],2))
[2, 3, 5, 6, 8, 9, 10] [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
Complexity¶
O(N log K)
O(N)
Approach 3 (Space in place)¶
import heapq
def k_sorted(nums,k):
min_heap = []
for i in range(k+1):
heapq.heappush(min_heap,nums[i])
n = len(nums)
write_pos = 0
for i in range(k+1,n):
nums[write_pos]= heapq.heappop(min_heap)
write_pos +=1
heapq.heappush(min_heap,nums[i])
while len(min_heap)>0:
nums[write_pos]= heapq.heappop(min_heap)
write_pos +=1
return nums
print(k_sorted([6, 5, 3, 2, 8, 10, 9],3))
print(k_sorted([1, 4, 5, 2, 3, 6, 7, 8, 9, 10],2))
[2, 3, 5, 6, 8, 9, 10] [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
Complexity¶
O(N log K)
O(K)
Replace elements by its rank in the array¶
Given an array of N integers, the task is to replace each element of the array by its rank in the array.
Input: 20 15 26 2 98 6
Output: 4 3 5 1 6 2
Input: 1 5 8 15 8 25 9
Output: 1 2 3 5 3 6 4
Approach 1 (Brute Force)¶
- create an array with both idx and val
- sort the array by val
- keep track of rank and build the result
def replace_rank(nums):
idx_nums = []
n = len(nums)
for i in range(n):
idx_nums.append((i,nums[i]))
sorted_idx_nums = sorted(idx_nums,key=lambda x:x[1])
result = [0] * n
rank =0
for i in range(n):
idx,_ = sorted_idx_nums[i]
if i ==0 or sorted_idx_nums[i][1] != sorted_idx_nums[i-1][1]:
rank +=1
result[idx] = rank
return result
print(replace_rank([20, 15, 26, 2, 98, 6]))
print(replace_rank([1, 5, 8, 15, 8 ,25, 9]))
[4, 3, 5, 1, 6, 2] [1, 2, 3, 5, 3, 6, 4]
Complexity¶
- N = len(nums)
O(N log N)
O(N)
Approach 2 (More cleaner and pythonic)¶
def replace_rank(nums):
sorted_idx_nums = sorted(enumerate(nums),key=lambda x:x[1])
n = len(nums)
result = [0] * n
rank =0
for i in range(n):
if i ==0 or sorted_idx_nums[i][1] != sorted_idx_nums[i-1][1]:
rank +=1
result[sorted_idx_nums[i][0]] = rank
return result
print(replace_rank([20, 15, 26, 2, 98, 6]))
print(replace_rank([1, 5, 8, 15, 8 ,25, 9]))
[4, 3, 5, 1, 6, 2] [1, 2, 3, 5, 3, 6, 4]
Approach 3 (Using heaps)¶
import heapq
def replace_rank(nums):
n = len(nums)
min_heap = []
for i in range(n):
heapq.heappush(min_heap,(nums[i],i))
result = [0]*n
rank = 0
last = None
while len(min_heap) >0:
val,idx = heapq.heappop(min_heap)
if val != last:
rank +=1
result[idx] = rank
last = val
return result
print(replace_rank([20, 15, 26, 2, 98, 6]))
print(replace_rank([1, 5, 8, 15, 8 ,25, 9]))
[4, 3, 5, 1, 6, 2] [1, 2, 3, 5, 3, 6, 4]
Complexity¶
N = len(nums)
O(N log N)
O(N )
Approach 4 (Pythonic and cleaner)¶
import heapq
def replace_rank(nums):
min_heap = [(num, idx) for idx, num in enumerate(nums)]
heapq.heapify(min_heap)
n = len(nums)
result = [0]*n
rank = 0
last = None
while min_heap:
val,idx = heapq.heappop(min_heap)
if val != last:
rank +=1
result[idx] = rank
last = val
return result
print(replace_rank([20, 15, 26, 2, 98, 6]))
print(replace_rank([1, 5, 8, 15, 8 ,25, 9]))
[4, 3, 5, 1, 6, 2] [1, 2, 3, 5, 3, 6, 4]
Kth largest element in a stream of running integers¶
Implement a class KthLargest to find the kth largest number in a stream. It should have the following methods:
KthLargest(int k, int [] nums) Initializes the object with the integer k and the initial stream of numbers in nums int add(int val) Appends the integer val to the stream and returns the kth largest element in the stream.
Note that it is the kth largest element in the sorted order, not the kth distinct element.
Input: [KthLargest(3, [1, 2, 3, 4]), add(5), add(2), add(7)]
Output: [null, 3, 3, 4]
Explanation: initial stream = [1, 2, 3, 4], k = 3.
add(5): stream = [1, 2, 3, 4, 5] -> returns 3
add(2): stream = [1, 2, 2, 3, 4, 5] -> returns 3
add(7): stream = [1, 2, 2, 3, 4, 5, 7] -> returns 4
Input: [KthLargest(2, [5, 5, 5, 5], add(2), add(6), add(60)]
Output: [null, 5, 5, 6]
Explanation: initial stream = [5, 5, 5, 5], k = 2.
add(2): stream = [5, 5, 5, 5, 2] -> returns 5
add(6): stream = [5, 5, 5, 5, 2, 6] -> returns 5
add(60): stream = [5, 5, 5, 5, 2, 6, 60] -> returns 6
Approach 1 (Min heap)¶
- while initializing , build a min heap of size k
- on add operation , add the element to min_heap, ensure heap size and return the kth element
- create a helper balance which will ensures size of heap cannot exceed k
import heapq
class KthLargest:
def __init__(self,k,nums):
self.min_heap = []
self.k = k
for num in nums:
heapq.heappush(self.min_heap,num)
self.balance()
def add(self,num):
heapq.heappush(self.min_heap, num)
self.balance()
# Return the kth largest (smallest in min-heap) without removing it
return self.min_heap[0]
def balance(self):
while len(self.min_heap) > self.k:
heapq.heappop(self.min_heap)
obj1 = KthLargest(3,[1, 2, 3, 4])
print(obj1.add(5))
print(obj1.add(2))
print(obj1.add(7))
obj1 = KthLargest(2,[5, 5, 5, 5])
print(obj1.add(2))
print(obj1.add(6))
print(obj1.add(60))
3 3 4 5 5 6
Complexity¶
N= len(nums)
O(N log(k)) for construcotr , O(log K) for add
O(K) for storing min_heap