786. K-th Smallest Prime Fraction

786. K-th Smallest Prime Fraction

10th May 2024 LeetCode Daily

ยท

5 min read

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 for i > 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
Binary search is a powerful search algorithm that works by repeatedly dividing the search space in half. It starts by considering the entire range of elements, then picks the middle element. If the target value is greater, the search continues in the upper half; if it's lower, the search continues in the lower half. This process of halving the search space continues until the target is found or it's determined to not exist within the range. Due to the repeated halving, the number of comparisons needed to find the target is significantly reduced, making it an efficient search technique for sorted data.
  1. 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.

  2. 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.

  3. 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".

  4. 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.

  5. 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...

  6. 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!

  7. 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".

  8. 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!๐Ÿš€

ย