Skip to content

feat: add solutions to lc problem: No.0440 #4469

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Jun 8, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,37 @@ tags:

<!-- solution:start -->

### 方法一
### 方法一:字典树计数 + 贪心构造

本题要求在区间 $[1, n]$ 中,按**字典序**排序后,找到第 $k$ 小的数字。由于 $n$ 的范围非常大(最多可达 $10^9$),我们无法直接枚举所有数字后排序。因此我们采用**贪心 + 字典树模拟**的策略。

我们将 $[1, n]$ 看作一棵 **十叉字典树(Trie)**:

- 每个节点是一个前缀,根节点为空串;
- 节点的子节点是当前前缀拼接上 $0 \sim 9$;
- 例如前缀 $1$ 会有子节点 $10, 11, \ldots, 19$,而 $10$ 会有 $100, 101, \ldots, 109$;
- 这种结构天然符合字典序遍历。

```
├── 1
│ ├── 10
│ ├── 11
│ ├── ...
├── 2
├── ...
```

我们使用变量 $\textit{curr}$ 表示当前前缀,初始为 $1$。每次我们尝试向下扩展前缀,直到找到第 $k$ 小的数字。

每次我们计算当前前缀下有多少个合法数字(即以 $\textit{curr}$ 为前缀、且不超过 $n$ 的整数个数),记作 $\textit{count}(\text{curr})$:

- 如果 $k \ge \text{count}(\text{curr})$:说明目标不在这棵子树中,跳过整棵子树,前缀右移:$\textit{curr} \leftarrow \text{curr} + 1$,并更新 $k \leftarrow k - \text{count}(\text{curr})$;
- 否则:说明目标在当前前缀的子树中,进入下一层:$\textit{curr} \leftarrow \text{curr} \times 10$,并消耗一个前缀:$k \leftarrow k - 1$。

每一层我们将当前区间扩大 $10$ 倍,向下延伸到更长的前缀,直到超出 $n$。

时间复杂度 $O(\log^2 n)$,空间复杂度 $O(1)$。

<!-- tabs:start -->

Expand Down Expand Up @@ -181,6 +211,75 @@ func findKthNumber(n int, k int) int {
}
```

#### TypeScript

```ts
function findKthNumber(n: number, k: number): number {
function count(curr: number): number {
let next = curr + 1;
let cnt = 0;
while (curr <= n) {
cnt += Math.min(n - curr + 1, next - curr);
curr *= 10;
next *= 10;
}
return cnt;
}

let curr = 1;
k--;

while (k > 0) {
const cnt = count(curr);
if (k >= cnt) {
k -= cnt;
curr += 1;
} else {
k -= 1;
curr *= 10;
}
}

return curr;
}
```

#### Rust

```rust
impl Solution {
pub fn find_kth_number(n: i32, k: i32) -> i32 {
fn count(mut curr: i64, n: i32) -> i32 {
let mut next = curr + 1;
let mut total = 0;
let n = n as i64;
while curr <= n {
total += std::cmp::min(n - curr + 1, next - curr);
curr *= 10;
next *= 10;
}
total as i32
}

let mut curr = 1;
let mut k = k - 1;

while k > 0 {
let cnt = count(curr as i64, n);
if k >= cnt {
k -= cnt;
curr += 1;
} else {
k -= 1;
curr *= 10;
}
}

curr
}
}
```

<!-- tabs:end -->

<!-- solution:end -->
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,46 @@ tags:

<!-- solution:start -->

### Solution 1
### Solution 1: Trie-Based Counting + Greedy Construction

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**.

We treat the range $[1, n]$ as a **10-ary prefix tree (Trie)**:

- Each node represents a numeric prefix, starting from an empty root;
- Each node has 10 children, corresponding to appending digits $0 \sim 9$;
- For example, prefix $1$ has children $10, 11, \ldots, 19$, and node $10$ has children $100, 101, \ldots, 109$;
- This tree naturally reflects lexicographical order traversal.

```
root
├── 1
│ ├── 10
│ ├── 11
│ ├── ...
├── 2
├── ...
```

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.

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})$:

- 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:

$$
\textit{curr} \leftarrow \textit{curr} + 1,\quad k \leftarrow k - \text{count}(\text{curr})
$$

- Otherwise: the target is within this subtree. We go one level deeper:

$$
\textit{curr} \leftarrow \textit{curr} \times 10,\quad k \leftarrow k - 1
$$

At each level, we enlarge the current range by multiplying by 10 and continue descending until we exceed $n$.

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.

<!-- tabs:start -->

Expand Down Expand Up @@ -179,6 +218,75 @@ func findKthNumber(n int, k int) int {
}
```

#### TypeScript

```ts
function findKthNumber(n: number, k: number): number {
function count(curr: number): number {
let next = curr + 1;
let cnt = 0;
while (curr <= n) {
cnt += Math.min(n - curr + 1, next - curr);
curr *= 10;
next *= 10;
}
return cnt;
}

let curr = 1;
k--;

while (k > 0) {
const cnt = count(curr);
if (k >= cnt) {
k -= cnt;
curr += 1;
} else {
k -= 1;
curr *= 10;
}
}

return curr;
}
```

#### Rust

```rust
impl Solution {
pub fn find_kth_number(n: i32, k: i32) -> i32 {
fn count(mut curr: i64, n: i32) -> i32 {
let mut next = curr + 1;
let mut total = 0;
let n = n as i64;
while curr <= n {
total += std::cmp::min(n - curr + 1, next - curr);
curr *= 10;
next *= 10;
}
total as i32
}

let mut curr = 1;
let mut k = k - 1;

while k > 0 {
let cnt = count(curr as i64, n);
if k >= cnt {
k -= cnt;
curr += 1;
} else {
k -= 1;
curr *= 10;
}
}

curr
}
}
```

<!-- tabs:end -->

<!-- solution:end -->
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
impl Solution {
pub fn find_kth_number(n: i32, k: i32) -> i32 {
fn count(mut curr: i64, n: i32) -> i32 {
let mut next = curr + 1;
let mut total = 0;
let n = n as i64;
while curr <= n {
total += std::cmp::min(n - curr + 1, next - curr);
curr *= 10;
next *= 10;
}
total as i32
}

let mut curr = 1;
let mut k = k - 1;

while k > 0 {
let cnt = count(curr as i64, n);
if k >= cnt {
k -= cnt;
curr += 1;
} else {
k -= 1;
curr *= 10;
}
}

curr
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
function findKthNumber(n: number, k: number): number {
function count(curr: number): number {
let next = curr + 1;
let cnt = 0;
while (curr <= n) {
cnt += Math.min(n - curr + 1, next - curr);
curr *= 10;
next *= 10;
}
return cnt;
}

let curr = 1;
k--;

while (k > 0) {
const cnt = count(curr);
if (k >= cnt) {
k -= cnt;
curr += 1;
} else {
k -= 1;
curr *= 10;
}
}

return curr;
}