At first glance, this problem looks like a simple sorting task, but the real learning begins when you try to do it without extra memory. It makes you think about how to use binary search in a slightly different way. In fact, this is a known favorite at Google, Amazon, Meta, Microsoft, and Bloomberg, so it’s worth practicing until the idea feels natural.
Problem statement
You are given an $n \times n$ matrix where each row and each column is sorted in ascending order. Return the $k^{th}$ smallest element in the matrix.
Note:
It’s the $k^{th}$ smallest in sorted order, not the $k^{th}$ distinct element (duplicates count).
Find a solution with a memory complexity better than $O(n^2)$.
Constraints:
- n $==$
matrix.length$==$matrix[i].length - $1 \leq$
n$\leq 300$ - $−10^9 \leq$
matrix[i][j]$\leq 10^9$ - All the rows and columns of
matrixare guaranteed to be sorted in non-decreasing order. - $1 \leq$
k$\leq n^2$
Examples
| Input | Output | Explanation |
|---|---|---|
| [[7]] 1 | 7 | The elements of matrix in flattened sorted order are [7]. The 1st smallest element is 7. |
| [[1, 2], [3, 4]] 3 | 3 | The elements of matrix in flattened sorted order are [1, 2, 3, 4]. The 3rd smallest element is 3. |
| [[1, 3, 3], [5, 6, 6], [11, 13, 13]] 6 | 6 | The elements of matrix in flattened sorted order are [1, 3, 3, 5, 6, 6, 11, 13, 13]. The 5th smallest element is 6. |
| [[-8, -3, 1], [-6, -1, 2], [-6, 0, 5]] 4 | -3 | The elements of matrix in flattened sorted order are [-8, -6, -6, -3, -1, 0, 1, 2, 5]. The 4th smallest element is -3. |
A Naive start
A first thought might be to solve the problem by flattening the entire matrix into a single list, sorting it, and then picking the $k^{th}$ element. This requires storing all $n^2$ elements in memory, which takes $O(n^2)$ space, and then sorting them takes $O(n^2 \log n)$ time. However, as the problem explicitly asks for a solution with memory usage better than $O(n^2)$, we need something better.
An optimized way: Using the Binary Search
After realizing we can’t afford to store and sort all $n^2$ values, the next question becomes: Can we find the $k^{th}$ smallest without fully ordering every element of the matrix?
The key observation is that the matrix is already partially ordered because every row and column is sorted. This means we don’t need to guess the answer by position, we can guess it by value. We know that the smallest value is at the top-left cell matrix[0][0] and the largest value is at the bottom-right cell matrix[n−1], so the $k^{th}$ smallest element must lie somewhere in the numeric range [matrix[0][0],matrix[n−1][n−1]].
Now, this allows us to find the $k^{th}$ smallest element efficiently using Binary Search on the value range. Instead of searching by index, we search by value. For any guessed value $x$, we count how many elements in the matrix are $\leq x$. If this count is less than $k$, $x$ is too small, so we search higher values. Otherwise, $x$ is big enough and we keep searching smaller values. Repeating this shrinks the value range until it collapses to a single number, which is the $k^{th}$ smallest element.
While binary search is efficient, we also need an efficient way to count how many elements are $\leq x$ without scanning all $n^2$ cells. For this, we use a staircase walk starting from the bottom-left corner of the matrix. If the current value is $\le x$, then everything above it in the same column is also $\le x$, so we add all those elements at once and move right. If the current value is $>x$, we move up to reach smaller values. As we only move up or right, we visit at most about $2n$ cells, not $n^2$.
Step-by-step algorithm
Let’s look at the algorithm steps of the solution:
- Set up the search range (by value):
n = len(matrix),low = matrix[0][0], andhigh = matrix[n - 1][n - 1]. - Start the binary search on the value range. While
low < high:- Compute
mid = (low + high) // 2. - Compute the number of elements that are
<= mid, i.e.,num_count = countLeq(matrix, mid).- Inside
countLeq, initializerow = n - 1(bottom row),col = 0(leftmost column), andnum_count = 0. - While
row >= 0andcol < n:- Update
current = matrix[row][col]. - If
current <= x, add all values above in this column, i.e.,num_count += row + 1, and move to the rightcol += 1. - Otherwise, move upward
row -= 1.
- Update
- Return
num_countonce the loop ends.
- Inside
- Shrink the search range based on
num_count.- If
num_count < k, the k-th smallest is larger thanmid, so setlow = mid + 1. - Otherwise, the k-th smallest is
midor smaller, so sethigh = mid.
- If
- Compute
- When
low == high, the loop ends, so returnlowas the final answer.
Here’s an illustration to help you visualize the solution:
Code implementation in Python
Now, let’s look at the code for this solution below:
Time complexity
- The binary search step takes $O(log(maxValue−minValue)$.
- We binary search over the value range from the smallest to the largest element. Each iteration cuts the range in half, so it takes about $O(\log_{2} (maxValue – minValue))$ iterations.
- The counting step takes $O(n)$.
- We start at the bottom-left and move only up or right. In the worst case, we can move up at most $n$ times and right at most $n$ times, at most about $2n$ moves in total. So, one ‘count how many ≤ x’ pass is linear in $n$.
The overall time complexity is $O(n \times \log(maxValue − minValue))$.
Space complexity
The space complexity of this solution is $O(1)$, meaning it uses constant extra memory.
Common mistakes to avoid
This problem is straightforward once it clicks, but small implementation mistakes are very common. Keep an eye for these common mistakes:
- Binary searching indices instead of values: The matrix is sorted, but not fully sorted as a flat array. Binary search works here because we search the value range, not positions.
- Flattening the matrix and sorting: This violates the space constraint (
O(n²)space) and is often an instant rejection in interviews. - Incorrect counting logic: A very common bug is:
- Forgetting to add
row + 1whenmatrix[row][col] <= mid. - Or, starting the staircase walk from the wrong corner (top-left does NOT work; bottom-left or top-right does).
- Forgetting to add
- Off-by-one errors in range updates
- Using
high = mid - 1instead ofhigh = mid. - Or, returning
midinstead of the finallow.
- Using