Hey coding pioneers! ๐งญ
Today, weโre tackling a Hard problem that blends greedy algorithms with lexicographical tree traversal โ a neat and elegant concept hiding behind a deceptively simple prompt.
Letโs dive into LeetCode 440: K-th Smallest in Lexicographical Order โ and weโll break it down in a clean, beginner-friendly way. ๐ก
๐ Problem Overview
Given two integers n
and k
, return the k-th smallest number in lexicographical (dictionary) order from the range [1, n].
๐งพ Example 1:
Input: n = 13, k = 2
Output: 10
Explanation: Lexicographical order โ [1,10,11,12,13,2,3,4,5,6,7,8,9]
๐งพ Example 2:
Input: n = 1, k = 1
Output: 1
๐ง Intuition & Strategy
This problem can be visualized as a 10-ary tree (prefix tree) rooted at 1
, where each node represents a number and each child adds another digit to it.
Think of lexicographical order as a preorder traversal of this virtual tree:
1 โ 10 โ 100...
- Then backtrack and go to
2 โ 20 โ 200...
, and so on.
Our goal is to traverse this tree until we reach the k-th node.
๐ Key Insight:
We donโt need to generate the entire order. Instead, we count how many nodes (i.e., numbers) are between a prefix a
and its sibling prefix b = a + 1
.
This count is called a โgapโ, and we use it to decide whether to:
-
Move to the next sibling (increment
a
) -
Go deeper in the subtree (multiply
a
by 10)
๐ getGap(a, b, n)
This helper function calculates how many numbers exist in the range [a, b)
under the lexicographical tree and up to n
.
E.g., getGap(1, 2, 13) returns how many numbers lie between 1 and 2:
=> [1, 10, 11, 12, 13] => 5
โ๏ธ C++ Code (Efficient)
class Solution {
public:
int findKthNumber(int n, int k) {
long ans = 1;
for (int i = 1; i < k;) {
const long gap = getGap(ans, ans + 1, n);
if (i + gap <= k) {
i += gap;
++ans;
} else {
++i;
ans *= 10;
}
}
return ans;
}
private:
long getGap(long a, long b, long n) {
long gap = 0;
while (a <= n) {
gap += min(n + 1, b) - a;
a *= 10;
b *= 10;
}
return gap;
}
};
๐ JavaScript Version
var findKthNumber = function(n, k) {
let curr = 1;
let i = 1;
const getGap = (a, b, n) => {
let gap = 0;
while (a <= n) {
gap += Math.min(n + 1, b) - a;
a *= 10;
b *= 10;
}
return gap;
};
while (i < k) {
let gap = getGap(curr, curr + 1, n);
if (i + gap <= k) {
i += gap;
curr++;
} else {
i++;
curr *= 10;
}
}
return curr;
};
๐ Python Version
class Solution:
def findKthNumber(self, n: int, k: int) -> int:
def getGap(a: int, b: int, n: int) -> int:
gap = 0
while a <= n:
gap += min(n + 1, b) - a
a *= 10
b *= 10
return gap
curr = 1
i = 1
while i < k:
gap = getGap(curr, curr + 1, n)
if i + gap <= k:
i += gap
curr += 1
else:
i += 1
curr *= 10
return curr
๐งช Test Case Scenarios
Input: n = 13, k = 2
Output: 10
โ Lexicographical: [1,10,11,12,13,2,3...]
Input: n = 100, k = 10
Output: 17
โ We skip prefixes based on calculated gaps.
Input: n = 1000000, k = 1000
Output: Efficient tree traversal returns result in milliseconds.
Input: n = 1, k = 1
Output: 1
โ Base case
๐งฎ Time & Space Complexity
Time Complexity: O(log n * log n)
- log n levels in the tree
- Each getGap call takes O(log n) time
Space Complexity: O(1)
- Constant extra space (no recursion or data structures)
โจ Wrap Up
This problem shines in combining greedy algorithms, prefix trees, and mathematical counting โ all without recursion or brute force. ๐
โ Itโs a perfect example of using structured exploration to cut down unnecessary computation.
If you learned something new today, consider following for more in-depth problem breakdowns and sharing this with your fellow devs! ๐จโ๐ป๐ฉโ๐ป
Keep building, keep exploring. ๐๐ข
Top comments (2)
well explained, was waiting for your article.
Haha, Thank you Anna.
Some comments may only be visible to logged-in visitors. Sign in to view all comments.