题目如下:
给定整数 n
和 k
,找到 1
到 n
中字典序第 k
小的数字。
注意:1 ≤ k ≤ n ≤ 109
这道题的难点在于字典序,字典序是按照前缀大小来排序的,比如11和2,实际11排在2的前面,和正常数字排序很不一样,因此需要抽象出字典序模型,即实际上是一个十叉树,从网上找了个图如下
因此只需要确定三件事就可以确定k的位置即:
怎么确定一个前缀下所有子节点的个数?
如果第 k 个数在当前的前缀下,怎么继续往下面的子节点找?
如果第 k 个数不在当前的前缀,即当前的前缀比较小,如何扩大前缀,增大寻找的范围?
确定前缀下所有子节点个数可通过一直往下层节点遍历,直到前缀达到最大值n时下一个前缀减去当前前缀来获得,当然要考虑n在当前前缀的情况和到下一个前缀的情况,即边界情况。
第一步这里我想了很久,后面才想明白,n即具体的数字,前缀也是具体的数字,只不过前缀下面也会有子节点。比如说前缀为11,则下面还有110,111等节点,如果n为123,即前缀11的下一个前缀12的下层第三个节点。
第二步,如果k在当前的前缀下,比如说123在12的前缀下,则树的深度需要往下一层,即12*10,同时当前位置也要加一,因为往下一层,节点也会+1
第三步,如果k不在当前的前缀下,比如123 不在120的前缀下,则前缀需要往后+1,同时当前位置数需要加上当前前缀和下一个前缀中间的个数
如此一直往后找,即可找到第k个数
附上代码片段
public int findKthNumber(int n, int k) {
// 当前位置,从1开始
int curr = 1;
// 前缀
int prefix = 1;
// 当前位置小于k则一直遍历
while (curr < k) {
// 每次查找当前前缀到下一个前缀且不大于n的节点的个数,即prefix峰的值
int temp = count(n, prefix);
// 如果当前位置加上prefix峰的数量大于k,则说明k就在当前子节点下面,则树往下探一层,同时当前位置+1
if (temp + curr > k) {
prefix*=10;
curr++;
} else { // 否则说明k不在当前前缀下面,则前缀+1,同时当前位置加上prefix峰的值
prefix++;
curr+=temp;
}
}
return prefix;
}
public int count(int n, int prefix) {
long curr = prefix;
// 下一个前缀峰头
long next = prefix + 1;
int count = 0;
while (curr <= n) {
// 两个峰头中间的个数,如果不够数量,那最大应该是n
count += Math.min(n+1, next) - curr;
// 如果说刚刚prefix是1,next是2,那么现在分别变成10和20
// 1为前缀的子节点增加10个,十叉树增加一层, 变成了两层
// 如果说现在prefix是10,next是20,那么现在分别变成100和200,
// 1为前缀的子节点增加100个,十叉树又增加了一层,变成了三层
curr*=10;
next*=10;
}
return count;
}