Problem Statement
You are given a sorted integer array arr
containing 1
and prime numbers, where all the integers of arr
are unique. You are also given an integer k
.
For every i
and j
where 0 <= i < j < arr.length
, we consider the fraction arr[i] / arr[j]
.
Return the kth
smallest fraction considered. Return your answer as an array of integers of size 2
, where answer[0] == arr[i]
and answer[1] == arr[j]
.
Examples
Example 1:
Input: arr = [1,2,3,5], k = 3
Output: [2,5]
Explanation: The fractions to be considered in sorted order are:
1/5, 1/3, 2/5, 1/2, 3/5, and 2/3.
The third fraction is 2/5.
Example 2:
Input: arr = [1,7], k = 1
Output: [1,7]
Constraints
2 <= arr.length <= 1000
1 <= arr[i] <= 3 * 10<sup>4</sup>
arr[0] == 1
arr[i]
is a prime number fori > 0
.All the numbers of
arr
are unique and sorted in strictly increasing order.1 <= k <= arr.length * (arr.length - 1) / 2
Intuition
In this problem we need to find the kth
smallest fraction formed by numbers in the array. Let's understand this by another example:
Suppose we're given [2,3,4,5] and k=2
. The fractions we can create with this array will be: [2/3, 2/4, 2/5, 3/4, 3/5, 4/5]
. Sorting these in ascending order will give: [2/5, 2/4, 3/5, 2/3, 3/4, 4/5]
. Now, the kth smallest, that is 2nd smallest fraction is 2/4
. Hence our result will be [2,4]
.
So, one thing we can do easily is to find all the possible fractions given the condition i>=0 and i<j and j<arr.length
. After doing that we can sort the resulting array in ascending order and then simply return the kth smallest fraction.
The above solution is a brute force solution. We can do a little better than this by using Binary Search.
What is binary search
How to use binary search?
Since the list is sorted, we can use Binary Search to efficiently find the
k-th
smallest fraction. Binary Search works by repeatedly guessing the middle value of a range and checking if it's the answer or where to look next.Setting the Stage: We start with a range from 0 (the smallest possible fraction) to 1 (the largest possible fraction). Then, we use two pointers, one for the lower bound and one for the upper bound, to shrink this range as we search.
Exploring Fractions: At each step, we take the middle value of the current range (let's call it "mid"). We then iterate through the list, comparing each element with "mid".
Counting Smaller Fractions: As we iterate, we keep track of how many fractions are smaller than or equal to "mid". This helps us decide where to look next.
Finding the Maximum Fraction: We also keep track of the largest fraction we've encountered so far within the current search range. This is important because...
Identifying the k-th Smallest: If we've found k or more fractions smaller than or equal to the maximum fraction, then that maximum fraction itself is the k-th smallest!
Refining the Search: Based on the number of smaller fractions we found:
If it's exactly k, we've found our answer and return the maximum fraction.
If it's more than k, we know the k-th smallest fraction must be to the left of the current "mid", so we adjust the search range by moving the upper bound to "mid".
If it's less than k, the k-th smallest fraction must be to the right, so we adjust the search range by moving the lower bound to "mid".
Repeating Until Found: We keep repeating steps 4-8 until we find the k-th smallest fraction or exhaust all possibilities.
This approach leverages Binary Search's efficiency and the sorted nature of the list to find the desired fraction quickly.
Let's checkout the code for this approach below.
Code Solution
Python Code for Binary Search:
class Solution:
def kthSmallestPrimeFraction(self, arr: List[int], k: int) -> List[int]:
# initialize left and right for binary search
left,right=0,1
# initialize len(arr) and res that we will return
n,res=len(arr),[]
# iterate till left is less than or equal to right
while left<=right:
# find mid of left and right
mid=left+(right-left)/2
# initialize j, total, current numerator, current denominator
j,total,numerator,denominator=1,0,0,0
# initialize maxFrac(maximum fraction we have found till now) to 0
maxFrac=0
# iterate over range of 'n'
for index in range(n):
# keep increasing j if j is less than n
# and arr[index] is greater than or equal to arr[j]*mid
# we are basically moving j to a point where
# the fraction formed is greater than mid
while j<n and arr[index]>=arr[j]*mid:
j+=1
# update total
total+=n-j
# upadte maxFrac to arr[index]*1.0/arr[j]
# numerator and denominator to index and j respectively
if j<n and maxFrac<arr[index]*1.0/arr[j]:
maxFrac=arr[index]*1.0/arr[j]
numerator,denominator=index,j
# update res and break out of for loop
# if total==k
if total==k:
res=[arr[numerator],arr[denominator]]
break
# update right to mid
# if total>k
# basically reducing search space to right side of array
if total>k:
right=mid
else:
# otherwise update left to mid
# basically reducing search space to left side of array
left=mid
return res
Time and Space complexity
Time Complexity:
$$O(n\ logn)$$
O(n)
for iterating over the loop inside binary search algorithm and O(logn)
for binary search.
Space Complexity:
$$O(1)$$
As we are not allocating any extra space.
Conclusion
Hope you understood how binary search could be used to solve this problem.
Hope that explanation made sense and you found this blog helpful. If you've made it this far, please give it a like and drop a comment.
Happy Coding!๐