Facebook Pixel

668. Kth Smallest Number in Multiplication Table

Problem Description

You're given a multiplication table of size m x n, where each cell at position [i][j] contains the product i * j (using 1-based indexing). For example, a 3x3 multiplication table would look like:

1  2  3
2  4  6
3  6  9

The task is to find the kth smallest element when all values in this multiplication table are considered in sorted order.

Input:

  • m: number of rows in the multiplication table
  • n: number of columns in the multiplication table
  • k: the position of the element to find (1-indexed)

Output:

  • The kth smallest value in the multiplication table

Example: For a 3x3 table with k = 5, the values in sorted order would be: [1, 2, 2, 3, 3, 4, 6, 6, 9]. The 5th smallest element is 3.

The solution uses binary search on the value range [1, m*n]. For each candidate value mid, it counts how many elements in the multiplication table are less than or equal to mid. This is done by iterating through each row i and calculating min(mid // i, n), which gives the count of elements in row i that are ≤ mid. If the total count is at least k, the answer is at most mid; otherwise, it must be larger than mid. The binary search continues until it converges on the exact kth smallest value.

Quick Interview Experience
Help others by sharing your interview experience
Have you seen this problem before?

Intuition

The key insight is that we don't need to generate the entire multiplication table to find the kth smallest element. Instead, we can use binary search on the possible values.

Think about it this way: the smallest possible value in the table is 1 (at position [1][1]), and the largest is m * n (at position [m][n]). The answer must be somewhere in this range.

For any given value x, we can efficiently count how many elements in the multiplication table are less than or equal to x without generating the table. How? For each row i, the elements are i, 2i, 3i, ..., n*i. The number of elements ≤ x in row i is simply min(x // i, n) - we divide x by i to see how many multiples of i are ≤ x, but cap it at n since each row has only n elements.

This counting ability enables a binary search strategy:

  • If we pick a value mid and find that there are at least k elements ≤ mid, then our answer is at most mid
  • If there are fewer than k elements ≤ mid, then our answer must be greater than mid

The beauty of this approach is that binary search will naturally converge to a value that actually exists in the table. Even though we're searching through all integers in the range [1, m*n], the final answer will always be a valid product that appears in the multiplication table because we're looking for the smallest value where the count reaches k.

Learn more about Math and Binary Search patterns.

Solution Approach

The implementation uses binary search to find the kth smallest element efficiently:

1. Initialize Search Range:

left, right = 1, m * n

We set the search boundaries to the minimum possible value (1) and maximum possible value (m * n) in the multiplication table.

2. Binary Search Loop:

while left < right:
    mid = (left + right) >> 1

We continue searching while left < right. The midpoint is calculated using bit shift (>> 1) which is equivalent to dividing by 2.

3. Count Elements ≤ mid:

cnt = 0
for i in range(1, m + 1):
    cnt += min(mid // i, n)

For each row i (from 1 to m), we count how many elements are less than or equal to mid:

  • mid // i gives us how many multiples of i are ≤ mid
  • We take min(mid // i, n) because each row has at most n elements
  • We accumulate these counts across all rows

4. Adjust Search Range:

if cnt >= k:
    right = mid
else:
    left = mid + 1
  • If cnt >= k, it means there are at least k elements ≤ mid, so our answer is at most mid. We move right to mid.
  • If cnt < k, it means there are fewer than k elements ≤ mid, so our answer must be greater than mid. We move left to mid + 1.

5. Return Result:

return left

When the loop ends, left equals right, and this value is our answer - the kth smallest element in the multiplication table.

The time complexity is O(m * log(m * n)) where the log factor comes from binary search and we spend O(m) time counting in each iteration. The space complexity is O(1) as we only use a few variables.

Ready to land your dream job?

Unlock your dream job with a 3-minute evaluator for a personalized learning plan!

Start Evaluator

Example Walkthrough

Let's walk through finding the 5th smallest element in a 3×3 multiplication table.

Setup:

  • m = 3, n = 3, k = 5
  • The multiplication table looks like:
1  2  3
2  4  6
3  6  9
  • Values in sorted order: [1, 2, 2, 3, 3, 4, 6, 6, 9]
  • We're looking for the 5th element, which should be 3

Binary Search Process:

Iteration 1:

  • left = 1, right = 9
  • mid = (1 + 9) / 2 = 5
  • Count elements ≤ 5:
    • Row 1: min(5/1, 3) = min(5, 3) = 3 elements (1, 2, 3)
    • Row 2: min(5/2, 3) = min(2, 3) = 2 elements (2, 4)
    • Row 3: min(5/3, 3) = min(1, 3) = 1 element (3)
    • Total count = 3 + 2 + 1 = 6
  • Since 6 ≥ 5, update right = 5

Iteration 2:

  • left = 1, right = 5
  • mid = (1 + 5) / 2 = 3
  • Count elements ≤ 3:
    • Row 1: min(3/1, 3) = min(3, 3) = 3 elements (1, 2, 3)
    • Row 2: min(3/2, 3) = min(1, 3) = 1 element (2)
    • Row 3: min(3/3, 3) = min(1, 3) = 1 element (3)
    • Total count = 3 + 1 + 1 = 5
  • Since 5 ≥ 5, update right = 3

Iteration 3:

  • left = 1, right = 3
  • mid = (1 + 3) / 2 = 2
  • Count elements ≤ 2:
    • Row 1: min(2/1, 3) = min(2, 3) = 2 elements (1, 2)
    • Row 2: min(2/2, 3) = min(1, 3) = 1 element (2)
    • Row 3: min(2/3, 3) = min(0, 3) = 0 elements
    • Total count = 2 + 1 + 0 = 3
  • Since 3 < 5, update left = 3

Final Result:

  • left = 3, right = 3
  • Loop terminates, return 3

The algorithm correctly identifies that 3 is the 5th smallest element. Notice how we never generated the full table - we only counted how many elements were less than or equal to our candidate values, making this approach much more efficient than sorting all m×n elements.

Solution Implementation

1class Solution:
2    def findKthNumber(self, m: int, n: int, k: int) -> int:
3        """
4        Find the kth smallest number in a multiplication table of size m x n.
5        Uses binary search on the value range to find the kth smallest element.
6      
7        Args:
8            m: Number of rows in the multiplication table
9            n: Number of columns in the multiplication table
10            k: The position of the element to find (1-indexed)
11      
12        Returns:
13            The kth smallest number in the multiplication table
14        """
15        # Binary search range: minimum value is 1, maximum value is m * n
16        left, right = 1, m * n
17      
18        # Binary search to find the kth smallest value
19        while left < right:
20            # Calculate middle value of current search range
21            mid = (left + right) // 2
22          
23            # Count how many numbers in the multiplication table are <= mid
24            count = 0
25            for row in range(1, m + 1):
26                # For each row i, elements are: i, 2i, 3i, ..., n*i
27                # Number of elements <= mid in row i is min(mid // i, n)
28                count += min(mid // row, n)
29          
30            # If count >= k, the kth number is at most mid
31            if count >= k:
32                right = mid
33            # If count < k, the kth number is greater than mid
34            else:
35                left = mid + 1
36      
37        # When left == right, we've found the kth smallest number
38        return left
39
1class Solution {
2    /**
3     * Finds the kth smallest number in an m x n multiplication table.
4     * Uses binary search to find the target value.
5     * 
6     * @param m Number of rows in the multiplication table
7     * @param n Number of columns in the multiplication table
8     * @param k The position of the number to find (1-indexed)
9     * @return The kth smallest number in the multiplication table
10     */
11    public int findKthNumber(int m, int n, int k) {
12        // Initialize binary search range
13        // Minimum possible value is 1 (1*1), maximum is m*n
14        int left = 1;
15        int right = m * n;
16      
17        // Binary search for the kth smallest number
18        while (left < right) {
19            // Calculate middle value using unsigned right shift to avoid overflow
20            int mid = (left + right) >>> 1;
21          
22            // Count how many numbers in the multiplication table are <= mid
23            int count = 0;
24            for (int row = 1; row <= m; row++) {
25                // For each row i, elements are: i*1, i*2, ..., i*n
26                // Count of elements <= mid in row i is min(mid/i, n)
27                count += Math.min(mid / row, n);
28            }
29          
30            // Adjust search range based on count
31            if (count >= k) {
32                // If count >= k, the kth number is at most mid
33                right = mid;
34            } else {
35                // If count < k, the kth number must be greater than mid
36                left = mid + 1;
37            }
38        }
39      
40        // When left == right, we've found the kth smallest number
41        return left;
42    }
43}
44
1class Solution {
2public:
3    int findKthNumber(int m, int n, int k) {
4        // Binary search on the answer range [1, m*n]
5        int left = 1;
6        int right = m * n;
7      
8        while (left < right) {
9            // Calculate middle value
10            int mid = left + (right - left) / 2;
11          
12            // Count how many numbers in the multiplication table are <= mid
13            int count = 0;
14            for (int row = 1; row <= m; ++row) {
15                // For each row i, elements are: i*1, i*2, ..., i*n
16                // Number of elements <= mid in row i is min(mid/i, n)
17                count += std::min(mid / row, n);
18            }
19          
20            // If count >= k, the kth smallest number is at most mid
21            if (count >= k) {
22                right = mid;
23            } else {
24                // Otherwise, the kth smallest number is greater than mid
25                left = mid + 1;
26            }
27        }
28      
29        // left == right, which is the kth smallest number
30        return left;
31    }
32};
33
1function findKthNumber(m: number, n: number, k: number): number {
2    // Binary search on the answer range [1, m*n]
3    let left: number = 1;
4    let right: number = m * n;
5  
6    while (left < right) {
7        // Calculate middle value using safe integer division
8        const mid: number = left + Math.floor((right - left) / 2);
9      
10        // Count how many numbers in the multiplication table are <= mid
11        let count: number = 0;
12        for (let row = 1; row <= m; row++) {
13            // For each row i, elements are: i*1, i*2, ..., i*n
14            // Number of elements <= mid in row i is min(mid/i, n)
15            count += Math.min(Math.floor(mid / row), n);
16        }
17      
18        // If count >= k, the kth smallest number is at most mid
19        if (count >= k) {
20            right = mid;
21        } else {
22            // Otherwise, the kth smallest number is greater than mid
23            left = mid + 1;
24        }
25    }
26  
27    // left == right, which is the kth smallest number
28    return left;
29}
30

Time and Space Complexity

Time Complexity: O(m * log(m * n))

The binary search runs from 1 to m * n, which means the search space has size m * n. Binary search takes O(log(m * n)) iterations to converge.

For each iteration of the binary search, we need to count how many elements in the multiplication table are less than or equal to mid. This counting process involves a loop that runs m times (from 1 to m), and for each iteration i, we calculate min(mid // i, n) which takes O(1) time.

Therefore, the total time complexity is O(m * log(m * n)).

Space Complexity: O(1)

The algorithm only uses a constant amount of extra space for variables left, right, mid, cnt, and the loop variable i. No additional data structures that scale with input size are used.

Learn more about how to find time and space complexity quickly.

Common Pitfalls

1. Off-by-One Error in Binary Search Boundary Update

A common mistake is incorrectly updating the binary search boundaries, particularly setting right = mid - 1 when count >= k:

Incorrect Implementation:

if count >= k:
    right = mid - 1  # WRONG! This might skip the actual answer
else:
    left = mid + 1

Why This Fails: When count >= k, mid could be the exact answer. Setting right = mid - 1 would exclude it from the search range. For example, if we're looking for the 5th smallest element and mid = 3 gives us exactly 5 elements ≤ 3, then 3 is our answer. But setting right = 2 would make us miss it.

Correct Approach:

if count >= k:
    right = mid  # Keep mid in the search range as potential answer
else:
    left = mid + 1

2. Incorrect Counting Logic

Another pitfall is misunderstanding how to count elements in each row:

Incorrect Implementation:

# Wrong: Not considering the table boundaries
count += mid // row  # Missing the min() with n

Why This Fails: Each row has exactly n elements. Without min(mid // row, n), we might count more elements than actually exist in a row. For instance, if mid = 100, row = 1, and n = 5, we'd incorrectly count 100 elements in the first row when there are only 5.

Correct Approach:

count += min(mid // row, n)  # Ensures we don't count beyond table boundaries

3. Using 0-Based Indexing for Rows

Incorrect Implementation:

for row in range(m):  # Wrong: starts from 0
    count += min(mid // (row + 1), n)  # Or forgetting to add 1

Why This Fails: The multiplication table uses 1-based indexing. Starting from row 0 would cause division by zero or incorrect calculations.

Correct Approach:

for row in range(1, m + 1):  # Start from 1, go up to m inclusive
    count += min(mid // row, n)

4. Integer Overflow in Large Inputs

While Python handles large integers automatically, in other languages or when optimizing:

Potential Issue:

mid = (left + right) // 2  # Could overflow in other languages

Safe Approach:

mid = left + (right - left) // 2  # Prevents overflow
# Or in Python, bit shift is also safe and efficient:
mid = (left + right) >> 1
Discover Your Strengths and Weaknesses: Take Our 3-Minute Quiz to Tailor Your Study Plan:

What is the best way of checking if an element exists in an unsorted array once in terms of time complexity? Select the best that applies.


Recommended Readings

Want a Structured Path to Master System Design Too? Don’t Miss This!

Load More