LeetCode 440
https://leetcode.cn/problems/k-th-smallest-in-lexicographical-order/description/
难度:困难
高频面试题汇总:https://www.yuweihung.com/posts/2025/lc-hot/
题解参考灵神(https://leetcode.cn/problems/k-th-smallest-in-lexicographical-order/solutions/3696254/zai-shi-cha-shu-shang-zhao-di-k-xiao-olo-vzkl/)。
本题可以看成一颗十叉树。
先序遍历这棵树,等价于从左到右遍历字典序列表。所以先序遍历访问到的第 k 个节点就是答案。
每访问一个节点,就把 k 减一。当 k=0 时,当前节点就是答案。
从根节点的第一个儿子 1 开始。由于该节点被我们访问了,先把 k 减一。
设子树 1 的大小(节点个数)为 size。分类讨论:
如果 size≤k,那么答案不在子树 1 中。然后考虑下一个儿子 2,依此类推。访问到下一个儿子时,要把 k 减去 (size−1)+1=size,这里 size−1 是因为节点 1 已经访问过了。
否则,答案在子树 1 中。首先访问 1 的儿子 10,把 k 减一。依次考虑 1 的儿子 10,11,12,…,19,计算子树大小,比较子树大小与 k 的大小关系,依此类推。 问:为什么判断条件是 size≤k 而不是 size<k?
答:因为当我们访问到子树根节点时,就把 k 减一了。这时再算子树大小,就把根节点重复统计了,所以是 size−1<k,即 size≤k。
如何计算子树大小 子树每层的节点个数是有规律的,可以逐层统计。
比如 n = 1234,计算子树 1 的大小:
第一层,只有 1 一个节点。 第二层,最小是 10,最大是 19,有 10 个节点。 第三层,最小是 100,最大是 199,有 100 个节点。 第四层,最小是 1000,最大是 n=1234,有 n−1000+1=235 个节点。 所以一共有 1+10+100+235=346 个节点。
上述过程如何用代码实现?
每一层的最小值是好算的:1→10→100→1000。
最大值呢?看上去不太好算。
不妨改为 2→20→200→min(2000,n+1),这个过程中的每个数减一,就是每一层的最大值。
时间复杂度:\(O(Dlog^2n)\),其中 D=10。整棵树有 O(logn) 层,每层会遍历至多 D 个节点,每个节点需要 O(logn) 的时间计算子树大小。
空间复杂度:O(1)。
class Solution {
public:
int findKthNumber(int n, int k) {
// 逐层统计 node 子树大小
auto count_subtree_size = [&](int node) -> int {
// 子树大小不会超过 n,所以 size 用 int 类型
// 但计算过程中的 left 和 right 会超过 int,所以用 long long 类型
int size = 0;
long long left = node, right = node + 1;
while (left <= n) {
// 这一层的最小值是 left,最大值是 min(right, n + 1) - 1
size += min(right, n + 1LL) - left;
left *= 10; // 继续,计算下一层
right *= 10;
}
return size;
};
int node = 1;
k--; // 访问节点 node
while (k > 0) {
int size = count_subtree_size(node);
if (size <= k) { // 向右,跳过 node 子树
node++; // 访问 node 右侧兄弟节点
k -= size; // 访问子树中的每个节点,以及新的 node 节点
} else { // 向下,深入 node 子树
node *= 10; // 访问 node 的第一个儿子
k--; // 访问新的 node 节点
}
}
return node;
}
};