LeetCode——字典序的第K小数字

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;
    }
posted @ 2020-04-28 10:58  Shaw_喆宇  阅读(351)  评论(0编辑  收藏  举报