Skip to content

Commit 26b007e

Browse files
authored
feat: add solutions to lc problem: No.0440 (#4469)
No.0440.K-th Smallest in Lexicographical Order
1 parent f5db36b commit 26b007e

File tree

4 files changed

+268
-2
lines changed

4 files changed

+268
-2
lines changed

solution/0400-0499/0440.K-th Smallest in Lexicographical Order/README.md

Lines changed: 100 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,37 @@ tags:
4949

5050
<!-- solution:start -->
5151

52-
### 方法一
52+
### 方法一:字典树计数 + 贪心构造
53+
54+
本题要求在区间 $[1, n]$ 中,按**字典序**排序后,找到第 $k$ 小的数字。由于 $n$ 的范围非常大(最多可达 $10^9$),我们无法直接枚举所有数字后排序。因此我们采用**贪心 + 字典树模拟**的策略。
55+
56+
我们将 $[1, n]$ 看作一棵 **十叉字典树(Trie)**
57+
58+
- 每个节点是一个前缀,根节点为空串;
59+
- 节点的子节点是当前前缀拼接上 $0 \sim 9$;
60+
- 例如前缀 $1$ 会有子节点 $10, 11, \ldots, 19$,而 $10$ 会有 $100, 101, \ldots, 109$;
61+
- 这种结构天然符合字典序遍历。
62+
63+
```
64+
65+
├── 1
66+
│ ├── 10
67+
│ ├── 11
68+
│ ├── ...
69+
├── 2
70+
├── ...
71+
```
72+
73+
我们使用变量 $\textit{curr}$ 表示当前前缀,初始为 $1$。每次我们尝试向下扩展前缀,直到找到第 $k$ 小的数字。
74+
75+
每次我们计算当前前缀下有多少个合法数字(即以 $\textit{curr}$ 为前缀、且不超过 $n$ 的整数个数),记作 $\textit{count}(\text{curr})$:
76+
77+
- 如果 $k \ge \text{count}(\text{curr})$:说明目标不在这棵子树中,跳过整棵子树,前缀右移:$\textit{curr} \leftarrow \text{curr} + 1$,并更新 $k \leftarrow k - \text{count}(\text{curr})$;
78+
- 否则:说明目标在当前前缀的子树中,进入下一层:$\textit{curr} \leftarrow \text{curr} \times 10$,并消耗一个前缀:$k \leftarrow k - 1$。
79+
80+
每一层我们将当前区间扩大 $10$ 倍,向下延伸到更长的前缀,直到超出 $n$。
81+
82+
时间复杂度 $O(\log^2 n)$,空间复杂度 $O(1)$。
5383

5484
<!-- tabs:start -->
5585

@@ -181,6 +211,75 @@ func findKthNumber(n int, k int) int {
181211
}
182212
```
183213

214+
#### TypeScript
215+
216+
```ts
217+
function findKthNumber(n: number, k: number): number {
218+
function count(curr: number): number {
219+
let next = curr + 1;
220+
let cnt = 0;
221+
while (curr <= n) {
222+
cnt += Math.min(n - curr + 1, next - curr);
223+
curr *= 10;
224+
next *= 10;
225+
}
226+
return cnt;
227+
}
228+
229+
let curr = 1;
230+
k--;
231+
232+
while (k > 0) {
233+
const cnt = count(curr);
234+
if (k >= cnt) {
235+
k -= cnt;
236+
curr += 1;
237+
} else {
238+
k -= 1;
239+
curr *= 10;
240+
}
241+
}
242+
243+
return curr;
244+
}
245+
```
246+
247+
#### Rust
248+
249+
```rust
250+
impl Solution {
251+
pub fn find_kth_number(n: i32, k: i32) -> i32 {
252+
fn count(mut curr: i64, n: i32) -> i32 {
253+
let mut next = curr + 1;
254+
let mut total = 0;
255+
let n = n as i64;
256+
while curr <= n {
257+
total += std::cmp::min(n - curr + 1, next - curr);
258+
curr *= 10;
259+
next *= 10;
260+
}
261+
total as i32
262+
}
263+
264+
let mut curr = 1;
265+
let mut k = k - 1;
266+
267+
while k > 0 {
268+
let cnt = count(curr as i64, n);
269+
if k >= cnt {
270+
k -= cnt;
271+
curr += 1;
272+
} else {
273+
k -= 1;
274+
curr *= 10;
275+
}
276+
}
277+
278+
curr
279+
}
280+
}
281+
```
282+
184283
<!-- tabs:end -->
185284

186285
<!-- solution:end -->

solution/0400-0499/0440.K-th Smallest in Lexicographical Order/README_EN.md

Lines changed: 109 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,46 @@ tags:
4747

4848
<!-- solution:start -->
4949

50-
### Solution 1
50+
### Solution 1: Trie-Based Counting + Greedy Construction
51+
52+
The problem asks for the \$k\$-th smallest number in the range $[1, n]$ when all numbers are sorted in **lexicographical order**. Since $n$ can be as large as $10^9$, we cannot afford to generate and sort all the numbers explicitly. Instead, we adopt a strategy based on **greedy traversal over a conceptual Trie**.
53+
54+
We treat the range $[1, n]$ as a **10-ary prefix tree (Trie)**:
55+
56+
- Each node represents a numeric prefix, starting from an empty root;
57+
- Each node has 10 children, corresponding to appending digits $0 \sim 9$;
58+
- For example, prefix $1$ has children $10, 11, \ldots, 19$, and node $10$ has children $100, 101, \ldots, 109$;
59+
- This tree naturally reflects lexicographical order traversal.
60+
61+
```
62+
root
63+
├── 1
64+
│ ├── 10
65+
│ ├── 11
66+
│ ├── ...
67+
├── 2
68+
├── ...
69+
```
70+
71+
We use a variable $\textit{curr}$ to denote the current prefix, initialized as $1$. At each step, we try to expand or skip prefixes until we find the \$k\$-th smallest number.
72+
73+
At each step, we calculate how many valid numbers (i.e., numbers $\le n$ with prefix $\textit{curr}$) exist under this prefix subtree. Let this count be $\textit{count}(\text{curr})$:
74+
75+
- If $k \ge \text{count}(\text{curr})$: the target number is not in this subtree. We skip the entire subtree by moving to the next sibling:
76+
77+
$$
78+
\textit{curr} \leftarrow \textit{curr} + 1,\quad k \leftarrow k - \text{count}(\text{curr})
79+
$$
80+
81+
- Otherwise: the target is within this subtree. We go one level deeper:
82+
83+
$$
84+
\textit{curr} \leftarrow \textit{curr} \times 10,\quad k \leftarrow k - 1
85+
$$
86+
87+
At each level, we enlarge the current range by multiplying by 10 and continue descending until we exceed $n$.
88+
89+
The time complexity is $O(\log^2 n)$, as we perform logarithmic operations for counting and traversing the Trie structure. The space complexity is $O(1)$ since we only use a few variables to track the current prefix and count.
5190

5291
<!-- tabs:start -->
5392

@@ -179,6 +218,75 @@ func findKthNumber(n int, k int) int {
179218
}
180219
```
181220

221+
#### TypeScript
222+
223+
```ts
224+
function findKthNumber(n: number, k: number): number {
225+
function count(curr: number): number {
226+
let next = curr + 1;
227+
let cnt = 0;
228+
while (curr <= n) {
229+
cnt += Math.min(n - curr + 1, next - curr);
230+
curr *= 10;
231+
next *= 10;
232+
}
233+
return cnt;
234+
}
235+
236+
let curr = 1;
237+
k--;
238+
239+
while (k > 0) {
240+
const cnt = count(curr);
241+
if (k >= cnt) {
242+
k -= cnt;
243+
curr += 1;
244+
} else {
245+
k -= 1;
246+
curr *= 10;
247+
}
248+
}
249+
250+
return curr;
251+
}
252+
```
253+
254+
#### Rust
255+
256+
```rust
257+
impl Solution {
258+
pub fn find_kth_number(n: i32, k: i32) -> i32 {
259+
fn count(mut curr: i64, n: i32) -> i32 {
260+
let mut next = curr + 1;
261+
let mut total = 0;
262+
let n = n as i64;
263+
while curr <= n {
264+
total += std::cmp::min(n - curr + 1, next - curr);
265+
curr *= 10;
266+
next *= 10;
267+
}
268+
total as i32
269+
}
270+
271+
let mut curr = 1;
272+
let mut k = k - 1;
273+
274+
while k > 0 {
275+
let cnt = count(curr as i64, n);
276+
if k >= cnt {
277+
k -= cnt;
278+
curr += 1;
279+
} else {
280+
k -= 1;
281+
curr *= 10;
282+
}
283+
}
284+
285+
curr
286+
}
287+
}
288+
```
289+
182290
<!-- tabs:end -->
183291

184292
<!-- solution:end -->
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
impl Solution {
2+
pub fn find_kth_number(n: i32, k: i32) -> i32 {
3+
fn count(mut curr: i64, n: i32) -> i32 {
4+
let mut next = curr + 1;
5+
let mut total = 0;
6+
let n = n as i64;
7+
while curr <= n {
8+
total += std::cmp::min(n - curr + 1, next - curr);
9+
curr *= 10;
10+
next *= 10;
11+
}
12+
total as i32
13+
}
14+
15+
let mut curr = 1;
16+
let mut k = k - 1;
17+
18+
while k > 0 {
19+
let cnt = count(curr as i64, n);
20+
if k >= cnt {
21+
k -= cnt;
22+
curr += 1;
23+
} else {
24+
k -= 1;
25+
curr *= 10;
26+
}
27+
}
28+
29+
curr
30+
}
31+
}
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
function findKthNumber(n: number, k: number): number {
2+
function count(curr: number): number {
3+
let next = curr + 1;
4+
let cnt = 0;
5+
while (curr <= n) {
6+
cnt += Math.min(n - curr + 1, next - curr);
7+
curr *= 10;
8+
next *= 10;
9+
}
10+
return cnt;
11+
}
12+
13+
let curr = 1;
14+
k--;
15+
16+
while (k > 0) {
17+
const cnt = count(curr);
18+
if (k >= cnt) {
19+
k -= cnt;
20+
curr += 1;
21+
} else {
22+
k -= 1;
23+
curr *= 10;
24+
}
25+
}
26+
27+
return curr;
28+
}

0 commit comments

Comments
 (0)