This README will explain the core logic behind finding the k-th lexicographically smallest number between 1 and n
for C++, Java, JavaScript, Python, and Go implementations.
The problem revolves around lexicographical order (dictionary-like order) for numbers. We aim to find the k-th smallest number in that order. The solution uses a prefix tree traversal approach, where we explore numbers level by level (digit by digit) as they grow.
At each step, we either:
- Dive deeper into the current prefix (e.g., from
1
to10
, then to100
, etc.) if the k-th number exists within the subtree of the current prefix. - Move to the next sibling prefix (e.g., from
1
to2
) if the k-th number doesn't lie within the current subtree.
We start at the smallest prefix (1
) and count how many numbers exist under each prefix to determine whether we should skip the prefix or explore it further.
curr
: Start with the first lexicographical prefix, which is1
.k
: Decrementk
by 1 because we already start at the first number (which is1
).
To determine whether the k-th number lies under the current prefix, we need to calculate how many numbers exist that begin with this prefix. The logic is as follows:
- Initialize
steps = 0
: This keeps track of how many valid numbers start with the current prefix. - Set bounds:
first
= curr: The smallest number starting with the current prefix.last
= curr: The largest number starting with the current prefix.
- Expand the range:
- Move to the next level by multiplying
first
andlast
by 10 (e.g., from1
to10
,19
,100
, etc.). - At each level, count the valid numbers between
first
andlast
(ensuring not to exceedn
).
- Move to the next level by multiplying
Now that we know how many numbers exist under the current prefix, we compare:
-
If
steps <= k
: The k-th number is not within the subtree of this prefix.- Action: Move to the next sibling prefix (
curr++
). - Update
k
: Subtract the number of steps because we skip over these numbers.
- Action: Move to the next sibling prefix (
-
If
steps > k
: The k-th number lies within this subtree.- Action: Dive deeper into the current prefix (
curr *= 10
). - Update
k
: Decrement by 1 because we're now moving to the next level.
- Action: Dive deeper into the current prefix (
Once k
reaches 0, the current prefix (curr
) is the k-th lexicographical number, and we return it.
countSteps
Function: A helper function that counts how many numbers exist in the lexicographical range starting withcurr
up ton
.findKthNumber
Function: The main function that iterates through prefixes, comparing steps withk
, and either dives deeper into the current prefix or moves to the next prefix untilk
reaches 0.
- Similar logic is implemented with helper and main functions:
countSteps
: Calculates steps from the current prefix.findKthNumber
: Traverses the tree-like structure of prefixes and returns the result.
countSteps
: Calculates the number of numbers with the current prefix.findKthNumber
: Finds the k-th number using the same prefix comparison strategy.
countSteps
: Returns the number of numbers that start with the current prefix.findKthNumber
: Implements the prefix traversal logic to determine the k-th lexicographical number.
countSteps
: Calculates the total number of numbers for a given prefix.findKthNumber
: Implements the main logic to explore and find the k-th smallest number lexicographically.
The logic behind finding the k-th lexicographical number is consistent across all languages, revolving around counting how many numbers exist under each prefix and either diving deeper into the prefix or skipping to the next one based on the comparison with k
. The efficiency comes from the fact that we don’t need to generate all numbers, but rather, we traverse them systematically like a prefix tree.