440. 字典序的第K小数字
给定整数 n 和 k,返回 [1, n] 中字典序第 k 小的数字。
示例 1:
输入: n = 13, k = 2
输出: 10
解释: 字典序的排列是 [1, 10, 11, 12, 13, 2, 3, 4, 5, 6, 7, 8, 9],所以第二小的数字是 10。
示例 2:
输入: n = 1, k = 1
输出: 1
提示:
1 <= k <= n <= 109
分析
今天的每日一题。
参考官方题解做的,官方题解:https://leetcode-cn.com/problems/k-th-smallest-in-lexicographical-order/solution/zi-dian-xu-de-di-kxiao-shu-zi-by-leetcod-bfy0/
下面写一下自己的理解:
上面是一颗字典树,利用前序遍历遍历到索引 k 得到的数字就是我们要的答案,问题在于,我们没有树,这该怎么办,那就想办法把遍历的思想拆解成几步做这道题。
1、以 i 为大的根节点(1 <= i <= 9),其第二层的数字为 10 * i ~ 10 * i + 9,第三层的数字为 100 * i ~ 100 * i + 10 * i + 9,以此类推找到最大的数字大于 n 但是最小的数字小于 n 的那一层,那么 n 就在这层数字中,如果该层最小的数字大于 n,说明根节点不够大(例如n = 25,i = 1时,10 ~ 19都小于25,下一层100~199都大于25,i 需要加一继续寻找);在找 n 的过程中记录经过的所有节点数目(相当于层序遍历记录每层节点数的过程),如果节点数目大于 k,说明目标节点在当前的根中(注意不是层中),就不必再继续往后找了
2、锁定了根之后,考虑如何找到第 k 个节点,如何进行前序遍历,假如说找到的根是 1,我们还是一层一层的找目标节点,利用节点数目是否大于 k 来判断在哪一层,按照前序遍历的思路每次把根节点换成当前根的子节点,例如我们知道以 1 为根的树总节点数大于 k,接下来以 10(即1 * 10)为根,如果还是大于 k,就以 100 为根,如果以 100 为根节点数小于 k 了,说明目标节点是以10为根的子节点,接下来开始对以101~109为根寻找目标节点,以此类推直到找到节点数刚好为 k。
3、以上思路实际上是个递归的过程,我们在代码中用迭代来实现,思路如下:
① 创建一个方法来计算当前根节点下小于等于 n 的总节点数,创建记录节点数目的变量 steps,利用 while 循环来计算,创建变量 first 和 last 等于根节点,那么该根节点的下面一层子节点范围是 first = first * 10~last = last * 10 + 9,总节点数(刚开始只有一个根节点)为 steps += min(last, n) - first + 1(需要考虑 n 在不在这一层中),再下一层子节点的范围是 first = first * 10~last = last * 10 + 9,,总节点数为 steps += min(last, n) - first + 1,这样循环体就确定了,循环退出条件是first > n。
② 用 curr 代表根节点,初始值为 1,判断以curr为根节点的树的节点数 steps 是否大于 k,如果不大于 k,k -= steps,并且根节点右移(curr = curr + 1),否则根节点下移(curr = curr * 10),循环退出条件是 k > 0。
4、补充:以上的根改变可以理解成找 n 的前缀的过程,例如对于n = 4652,上述根的变化是:1->2->3->4->40->41->42->43->44->45->46->461->462->463->464->465->4651->4652。同样本题可以用递归实现求解节点数目。
题解(Java)
class Solution {
public int findKthNumber(int n, int k) {
int curr = 1;
k--;
while (k > 0) {
int steps = getSteps(curr, n);
if (steps <= k) { //判断当前根节点下节点总数是否大于 k
k -= steps;
curr++;
} else {
curr = curr * 10;
k--;
}
}
return curr;
}
public int getSteps(int curr, long n) { //获取以 curr 为根的节点总数的方法
int steps = 0;
long first = curr;
long last = curr;
while (first <= n) {
steps += Math.min(last, n) - first + 1;
first = first * 10;
last = last * 10 + 9;
}
return steps;
}
}
递归:
class Solution {
public int findKthNumber(int n, int k) {
int cur = 1;
k--;
while(k > 0) {
long cnts = dfs(cur, cur, n);
if(cnts <= k) {
k -= cnts;
cur++;
} else {
k--;
cur *= 10;
}
}
return cur;
}
private long dfs(long l, long r, int n) {
if(l > n) {
return 0;
}
return Math.min(r, n) - l + 1 + dfs(l * 10, r * 10 + 9, n);
}
}