440. 字典序的第K小数字 - 力扣(LeetCode) (leetcode-cn.com)
思路
构建数字字典树:
可以在
n
n
n的范围内建出这个字典树。找K小数,考虑朴素算法:深搜从上到下,从左到右,一个一个搜,搜
K
K
K次找到的就是
K
K
K小数。如
n
=
109
,
k
=
103
n = 109,k = 103
n=109,k=103, 那么找到路线就是1-10-100-101-102-103
。
这个做法复杂度显然是 O ( K ) O(K) O(K)的,数据范围在 1 e 9 1e9 1e9会超时,所以考虑优化:由于该树除了包含 n n n的子树外,其他位置都是十叉,所以可以一次性统计一个节点的子树中的节点个数。
所以算法流程是:从 1 1 1节点开始,如果 K > = K >= K>= 当前子树中的节点个数 + 1 \ + 1 +1,则答案一定不在这个子树内,直接跳到同层的下一个子树;如果 K < K < K<当前子树中的节点个数,则答案就在该子树中,向下走一层。继续上述过程直到找到答案。
另外,如何统计当前子树中的节点个数
c
n
t
cnt
cnt呢?由于受到
n
n
n的限制,每个节点子树的深度有限且每个元素不会超过
n
n
n。所以计算一层中最右侧节点大小,如第一层为
19
19
19,第二层为
199
199
199,如果超过
n
n
n则取
n
n
n,当前层节点数表示为代码就是 cnt += min(n, right) - left + 1
。只要
l
e
f
t
left
left节点
<
n
<n
<n就一直向下统计每一层。
由于统计子树大小 l o g n logn logn,跳节点 l o g n logn logn次,故总复杂度 O ( l o g 2 n ) O(log^2n) O(log2n)。
代码
/*
* @Author: 爱学习的图灵机
* @Date: 2022-03-23 18:45:23
* @LastEditTime: 2022-03-23 18:45:23
* Bilibili:https://space.bilibili.com/7469540
* 题目地址:https://leetcode-cn.com/problems/k-th-smallest-in-lexicographical-order/
* @keywords: 树
*/
typedef long long LL;
class Solution {
public:
int get_cnt(LL u, LL n){ // <= n 的条件下,u的子树中的节点数量(包括u)
int cnt = 0;
LL left = u, right = u; // 一层中的左子节点和右子结点 如100 - 199
// 注意,初始只有一个的节点也被统计了
while(left <= n){
cnt += min(n, right) - left + 1;
right = right * 10 + 9;
left *= 10;
}
return cnt;
}
int findKthNumber(int n, int k) {
-- k; // 1已经用掉一个点
int cur = 1; // 最开始节点1
while(k > 0){
int count = get_cnt(cur, n);
if(k >= count){
cur ++; // 向右跳
k -= count;
}else{
cur *= 10; // 向下跳
-- k;
}
}
return cur;
}
};