Q:给定整数 n 和 k,找到 1 到 n 中字典序第 k 小的数字。
注意:(1 ≤ k ≤ n ≤ 10^9)。
示例 :
输入:
n: 13 k: 2
输出:
10
解释:
字典序的排列是 [1, 10, 11, 12, 13, 2, 3, 4, 5, 6, 7, 8, 9],所以第二小的数字是 10。
A:
1.用回溯法做
private int count;
private int res;
public int findKthNumber(int n, int k) {
count = k;
res = -1;
dfs(0, n);
return res;
}
private void dfs(int i, int n) {
if (i > n || res != -1)
return;
if (i != 0) {
count--;
if (count == 0) {
res = i;
}
}
for (int j = 0; j <= 9; j++) {
int curr = 10 * i + j;
if (curr != 0)
dfs(curr, n);
}
}
然后我就超时了……赤裸裸的超时了……
2.dfs剪枝(感谢@jiaxin)
最大的亮点在于观察上下界去计算每层的node数量,进而减少重复计算。我们可以建立一个字典树一样的结构。思路就是遍历了,但是每个都遍历不现实,所以我们不妨去计算一下他在哪个node对应的集合中。
我们可以先算出来这个数值在第一层哪个node集合里面,再去看对应第二层的哪个node集合,最后找到那个走了k步的node 就是我们的答案了。
下面说node集合数量怎么计算:
通过图我们会发现每层的node他的上界实际是下一个node对应层的最小值。所以计算该层node数量只需要 ((curr+1)*10-curr*10)就好,但是他还有一个上界就是最大值。
故每层的数量就是 (min((curr+1)*10,max\_value+1)-curr*10);计算这个node 有多少节点就把符合条件的每一层加起来就好
代码:
public int findKthNumber(int n, int k) {
int prefix = 1;
k--;//k记录要找的数字在prefix后的第几个
while (k > 0) {
long cnt = getCnt(n, prefix, prefix + 1);// 当前prefix下有多少个元素;包含prefix
if (cnt <= k) {// 向右
k -= cnt;
prefix++;
} else {// 向下
k--;
prefix *= 10;
}
}
return prefix;
}
private long getCnt(long n, long n1, long n2) {//这边用long,如果有int,有可能会超
long cnt = 0;
while (n1 <= n) {
cnt += Math.min(n + 1, n2) - n1;
n1 *= 10;
n2 *= 10;
}
return cnt;
}