So there is dfs way to calculate nth number of lexicographical ascending order
this dfs way is like traversing a tree of preorder traversal root->left->right
(root)
/ | \ ...
1 2 3
/ \
10 11
/ \
100 101
class Solution:
def findKthNumber(self, n: int, k: int) -> int:
count=0
ans=0
# 1 10 100 101 102 103 11 110 111 112 113
def dfs(num):
nonlocal count,ans
count+=1
if count==k:
ans=num
return
for i in range(10):
nextNum= num*10+i
if nextNum<=n:
dfs(nextNum)
for i in range(1,10):
if n>=i:
dfs(i)
return ans
or the iterative way but still tle
class Solution:
def findKthNumber(self, n: int, k: int) -> int:
count=0
curNum=1
# 1 10 100 101 102 103 11 110 111 112 113
for _ in range(n):
count+=1
if count==k:
break
if curNum*10<=n:
curNum*=10
else:
if curNum>=n:
curNum = curNum//10
curNum+=1
while curNum%10==0:
curNum = curNum//10
return curNum
so logic is quite hard. even harder than iterative. But it is still using the same logic of counting all valid numbers within the subtree using preorder traversal. Using [curNum, curNum+1] intervals, we can rapidly calculate how many numbers can be prefixed with curNum with this
count+= min(n+1, interval[1])-interval[0]
For curNum = 1, it sums up numbers like 1, then 10-19, then 100-199, and so on, stopping at n. The count for curNum=1 (with n=13) is 5 (1, 10, 11, 12, 13).
Then, even if count is not enough like it is less than k, we need to increment curNum by 1 to search for the next subtree. This is cuz currrent subtree does not produce enough numbers that can be prefixed with current number.
Else, there is indeed a value in the current subtree. So we multiply curNum by 10 and search deeper into the subtree. But i dont get why we k-=1 and not k minus the current level of the subtree like if it is 10 and there is 10,11,12 we should minus 3?
Correction: oh it is just k-=1 cuz for example if we are going from 1 to 10, we are saying we choose 1 as part of the path. We dont do k-=3 even when curNum=10 cuz we are not skipping 10,11,12.
class Solution:
def findKthNumber(self, n: int, k: int) -> int:
k-=1
curNum=1
# 1 10 100 101 102 103 11 110 111 112 113
while k:
count=0
interval=[curNum,curNum+1]
while interval[0]<=n:
count+= min(n+1, interval[1])-interval[0]
interval = [10*interval[0],10*interval[1]]
if k>=count:
curNum+=1
k-=count
else:
curNum*=10
k-=1
return curNum
It's quite common to find logarithmic time complexities tricky, especially when they are nested! Let's break down why the time complexity of this algorithm is .
Imagine the numbers from 1 to N organized in a tree structure:
(Root - conceptual)
/ | \ ... \
1 2 3 ... 9
/|\ /|\
10 11 20 21
/
100
...
Lexicographical order is simply a pre-order traversal of this tree (visit root, then its children from left to right).
Your code has two nested while loops that determine the time complexity:
while k > 0 loop: This loop makes "major decisions" about our path in the tree.while interval[0] <= n loop: This loop calculates the count (the number of nodes in a branch/subtree).Let's analyze each one:
while k > 0)What it does: In each iteration, result either:
result *= 10): This means we are going one level deeper into the tree (e.g., from 1 to 10, or 12 to 120).result += 1): This means we are moving sideways to the next sibling node at the current level (e.g., from 1 to 2, or 10 to 11, or 19 (backtracking) to 2).How many iterations?
N. If N = 10^9, it has 10 digits. To go from 1 to 1,000,000,000 (which is ), you multiply by 10 nine times. This is steps.result might need to increment multiple times (e.g., 1 -> 2 -> ... -> 9). In the worst case, result might need to jump from 1... to 9.... However, k is simultaneously decreasing. Across all levels, the total number of times result increments by 1 is also bounded.result variable is either increasing its number of digits or increasing its value within the current number of digits. Both of these combined ensure that result grows towards N. The number of "major decisions" (iterations of the outer loop) is roughly proportional to the number of digits in N, multiplied by a small constant (for the 9 possible siblings at each level). So, the outer loop runs approximately times.while interval[0] <= n)count of numbers in a particular branch. It starts with interval[0] (which is equal to result at the beginning of the inner loop) and repeatedly multiplies interval[0] by 10.result is 1, interval[0] will take values 1, 10, 100, 1000, ..., until it exceeds N.1 by 10 before it exceeds N is precisely the number of digits in N (or more accurately, ).space is 1