字典序的第K小数字

177 阅读3分钟

Offer 驾到,掘友接招!我正在参与2022春招打卡活动,点击查看活动详情

440. 字典序的第K小数字

概述

给定整数n和k,找到1到n中字典序第k小的数字,其中1kn1091 \leq k \leq n \leq 10^9

据说是字节跳动面试高频题,比较有意思,可惜并没有自主解决该问题。

思路

参考高赞题解,整体思路类似数位DP中的分析方法,可以建一颗树来对该问题进行讨论,从高位到低位,除了第一层是1~9以外,其它层均为0~9的十叉树。并且,在该树中,先序遍历即是按字典序从小到大的顺序遍历。那么,对于一个前缀prefix,一方面,我们需要知道当前前缀在先序遍历中的位次,例如前缀1本身的遍历位次为1,10的遍历位次为2。另一方面,我们需要判断目标值是否在在当前前缀代表的子树下。

因此,有三个问题需要解决:

  1. 如何判断某个前缀下的子树节点个数(包括当前前缀)
  2. 判断目标值是否在当前子树下
    • 若在当前子树,具体操作
    • 若不在当前子树,具体操作

判断前缀子树的节点个数,如果没有约束,那么其实很简单,当前前缀到最大层数依次是100+101+102+...10^0 + 10^1 + 10^2 + ...等,但是由于我们有最大的限制,所以没有这么简单。例如求前缀1下面的节点个数,某些数可能超过不能选,我们可以判断n和2???的大小关系,如果n比2????大,那么就全部可以选择,否则只能选到n的位置,这里注意n本身也可以被选择的细节,具体见代码实现。

对于第二个问题,有了判断前缀子树节点个数的方法,依次遍历的时候就可以知道当前前缀的位次与当前前缀子树的节点个数,若当前前缀的位次+当前前缀子树的节点个数大于目标值,说明节点在当前前缀子树下,前缀需要向深一层扩展。否则,节点不在当前前缀子树下,需要增大前缀,即前缀+1。

本题存在一定的思维难度,首先需要想到字典树,但不能显性建树,另外主要难点在于如何准确计算子树的节点个数。

Code

class Solution {
public:
    int findKthNumber(int n, int k) {
        auto get = [](long prefix, int n) {
            int res = 0;
            auto next = prefix + 1;
            while(prefix <= n) {
                //由于n本身可选,因此是n + 1,和next的最小值
                res += fmin(n + 1, next) - prefix;
                prefix *= 10, next *= 10;
            }
            return res;
        };
        int prefix = 1, cur = 1;
        while(cur < k) {
            int count = get(prefix, n);
            //在当前前缀子树内
            if(cur + count > k) {
                //前缀往深一层走,当前位次+1
                prefix *= 10;
                cur ++;
            }
            //不在当前前缀子树内
            else {
                //当前位次加上当前前缀子树的节点个数,然后扩大前缀
                prefix ++;
                cur += count;
            }
        }
        return prefix;
    }
};

复杂度分析

  • 时间复杂度O(log10N)O(log_{10}N)
  • 空间复杂度O(1)O(1)


欢迎讨论指正