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 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 template with first_true_index.
6        Feasible condition: count of elements <= mid is >= k
7        """
8        left, right = 1, m * n
9        first_true_index = -1
10
11        while left <= right:
12            mid = (left + right) // 2
13
14            # Count how many numbers in the multiplication table are <= mid
15            count = 0
16            for row in range(1, m + 1):
17                # For each row i, elements are: i, 2i, 3i, ..., n*i
18                # Number of elements <= mid in row i is min(mid // i, n)
19                count += min(mid // row, n)
20
21            # Feasible: count >= k means the kth number is at most mid
22            if count >= k:
23                first_true_index = mid
24                right = mid - 1
25            else:
26                left = mid + 1
27
28        return first_true_index
29
1class Solution {
2    /**
3     * Finds the kth smallest number in an m x n multiplication table.
4     * Uses binary search template with firstTrueIndex.
5     */
6    public int findKthNumber(int m, int n, int k) {
7        int left = 1;
8        int right = m * n;
9        int firstTrueIndex = -1;
10
11        while (left <= right) {
12            int mid = left + (right - left) / 2;
13
14            // Count how many numbers in the multiplication table are <= mid
15            int count = 0;
16            for (int row = 1; row <= m; row++) {
17                count += Math.min(mid / row, n);
18            }
19
20            // Feasible: count >= k means the kth number is at most mid
21            if (count >= k) {
22                firstTrueIndex = mid;
23                right = mid - 1;
24            } else {
25                left = mid + 1;
26            }
27        }
28
29        return firstTrueIndex;
30    }
31}
32
1class Solution {
2public:
3    int findKthNumber(int m, int n, int k) {
4        int left = 1;
5        int right = m * n;
6        int firstTrueIndex = -1;
7
8        while (left <= right) {
9            int mid = left + (right - left) / 2;
10
11            // Count how many numbers in the multiplication table are <= mid
12            int count = 0;
13            for (int row = 1; row <= m; ++row) {
14                count += std::min(mid / row, n);
15            }
16
17            // Feasible: count >= k means the kth number is at most mid
18            if (count >= k) {
19                firstTrueIndex = mid;
20                right = mid - 1;
21            } else {
22                left = mid + 1;
23            }
24        }
25
26        return firstTrueIndex;
27    }
28};
29
1function findKthNumber(m: number, n: number, k: number): number {
2    let left = 1;
3    let right = m * n;
4    let firstTrueIndex = -1;
5
6    while (left <= right) {
7        const mid = Math.floor((left + right) / 2);
8
9        // Count how many numbers in the multiplication table are <= mid
10        let count = 0;
11        for (let row = 1; row <= m; row++) {
12            count += Math.min(Math.floor(mid / row), n);
13        }
14
15        // Feasible: count >= k means the kth number is at most mid
16        if (count >= k) {
17            firstTrueIndex = mid;
18            right = mid - 1;
19        } else {
20            left = mid + 1;
21        }
22    }
23
24    return firstTrueIndex;
25}
26

Solution Approach

We use the standard binary search template with first_true_index to find the kth smallest element.

Binary Search Setup:

  • left = 1: minimum possible value in the table
  • right = m * n: maximum possible value in the table
  • first_true_index = -1: tracks the first value where count >= k

Feasible Condition: For any value mid, we count how many elements in the multiplication table are ≤ mid. If this count is >= k, then mid could be our answer (or the answer is smaller). This creates the monotonic pattern: false, false, ..., true, true, true.

Counting Elements ≤ mid: For each row i (from 1 to m), the elements are i, 2i, 3i, ..., n*i. The count of elements ≤ mid in row i is min(mid // i, n):

  • mid // i gives how many multiples of i are ≤ mid
  • We cap at n because each row has at most n elements

Binary Search Template:

while left <= right:
    mid = (left + right) // 2
    count = sum(min(mid // row, n) for row in range(1, m + 1))

    if count >= k:  # Feasible: kth element is at most mid
        first_true_index = mid
        right = mid - 1
    else:
        left = mid + 1

return first_true_index

Why the Result Exists in the Table: The binary search finds the smallest value where count >= k. This value is guaranteed to exist in the multiplication table because:

  • If mid is in the table and count(mid) >= k while count(mid-1) < k, then mid is exactly the kth smallest
  • The template converges to this exact boundary

The time complexity is O(m * log(m * n)) - O(log(m * n)) iterations with O(m) counting each. Space complexity is O(1).

Ready to land your dream job?

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

Start Evaluator

Example Walkthrough

Let's trace through the binary search template with m = 3, n = 3, k = 5.

Setup:

  • Multiplication table:
1  2  3
2  4  6
3  6  9
  • Sorted values: [1, 2, 2, 3, 3, 4, 6, 6, 9]
  • Looking for 5th element (should be 3)

Initial state:

  • left = 1, right = 9, first_true_index = -1
  • Feasible condition: count of elements ≤ mid is >= 5

Iteration 1:

  • mid = (1 + 9) // 2 = 5
  • Count elements ≤ 5:
    • Row 1: min(5/1, 3) = 3 elements (1, 2, 3)
    • Row 2: min(5/2, 3) = 2 elements (2, 4)
    • Row 3: min(5/3, 3) = 1 element (3)
    • Total = 6
  • Is 6 >= 5? Yes → Feasible
  • Update: first_true_index = 5, right = 4

Iteration 2:

  • left = 1, right = 4
  • mid = (1 + 4) // 2 = 2
  • Count elements ≤ 2:
    • Row 1: min(2/1, 3) = 2 elements (1, 2)
    • Row 2: min(2/2, 3) = 1 element (2)
    • Row 3: min(2/3, 3) = 0 elements
    • Total = 3
  • Is 3 >= 5? No → Not feasible
  • Update: left = 3

Iteration 3:

  • left = 3, right = 4
  • mid = (3 + 4) // 2 = 3
  • Count elements ≤ 3:
    • Row 1: min(3/1, 3) = 3 elements
    • Row 2: min(3/2, 3) = 1 element
    • Row 3: min(3/3, 3) = 1 element
    • Total = 5
  • Is 5 >= 5? Yes → Feasible
  • Update: first_true_index = 3, right = 2

Iteration 4:

  • left = 3, right = 2
  • Loop ends: left > right

Result:

  • first_true_index = 3
  • The 5th smallest element is 3

The template correctly finds the boundary where count first reaches k.

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. Using Wrong Binary Search Template Variant

The Pitfall: A common mistake is using while left < right with right = mid instead of the standard template with while left <= right and right = mid - 1.

Incorrect (non-standard template):

while left < right:
    mid = (left + right) // 2
    if count >= k:
        right = mid
    else:
        left = mid + 1
return left

Correct (standard template):

first_true_index = -1
while left <= right:
    mid = (left + right) // 2
    if count >= k:
        first_true_index = mid
        right = mid - 1
    else:
        left = mid + 1
return first_true_index

The standard template with first_true_index clearly tracks the answer and handles edge cases consistently.

2. Incorrect Counting Logic

The Pitfall: Not considering the table boundaries when counting:

Incorrect:

count += mid // row  # Missing the min() with n

Why This Fails: Each row has exactly n elements. Without min(mid // row, n), we count more elements than exist. If mid = 100, row = 1, and n = 5, we'd count 100 elements instead of 5.

Correct:

count += min(mid // row, n)

3. Using 0-Based Indexing for Rows

Incorrect:

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

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

Correct:

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

In languages with fixed integer sizes, calculating left + right can overflow.

Safe Approach:

mid = left + (right - left) // 2  # Prevents overflow
Loading...
Discover Your Strengths and Weaknesses: Take Our 5-Minute Quiz to Tailor Your Study Plan:

Which of the following is a min heap?


Recommended Readings

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

Load More