(1)首先列出所有的字典序,然后选择第k个

class Solution {
public:
    bool static compare(int a,int b)
    {
        return std::to_string(a) < std::to_string(b);
    }
    int findKthNumber(int n, int k) {
            vector<int>  Pool(n, 0);
            for (int i=0;i<n;i++)
            {
                Pool[i] = i+1;
            }
            sort(Pool.begin(),Pool.end(),compare);
            return Pool[k-1];
    }
};

可以看到,非常的简介明了,主要是用了sort()进行了排序,排序的算法就是转换为字符串,然后比较非常的简单明了。可惜了,这个的时间复杂度过不去,耗时要比较久。但是,这个给了我们生成字典序的一种方式。可以通过string比较的方式,快速生成字典序。

(2) 用树的方式

   如下图,所示,整个的字典序的排序方式,就是这个10叉树的一个优序遍历。每一个节点都拥有 10 个孩子节点,因为作为一个前缀 ,它后面可以接 0~9 这十个数字。而且你可以非常容易地发现,整个字典序排列也就是对十叉树进行先序遍历。1, 10, 100, 101, ... 11, 110 

所以,如果我们可以计算出每个前缀下面有多少个数字,那么就课可以很快的完成这个事情,比如说 1的前缀下面的有 1 10 11 12 ....18 19 100 101 102 ......198  199 等。我们是可以计算出来。

 数字n,按字典排序,找出第k小的数字

getTheNumberOfFix(prefix,n) 就是计算当前的前缀下,有多少个数字,prefix就是前缀,n是上限。主要的思路是当前的前缀的和下一个的前缀的之间的数量和,就是当前前缀的所有的个数。

class Solution {
public:
    int getTheNumberOfFix(int prefix, int n)
    {
        long long  currentFix=prefix;
        long long  nextFix=currentFix+1;
        int totolNumber=0;
        while(currentFix<=n)
        {
            totolNumber +=nextFix-currentFix;
            currentFix *=10;
            if(nextFix*10<n+1)
            {
                nextFix=nextFix*10;
            }
            else
            {
                nextFix=n+1;
            }
        }
        return totolNumber;
    }
    int findKthNumber(int n, int k) {
        int currentPosition=1;
        long long  prefix=1;
        while(currentPosition<k)
        {
            int numberOFcurrentFix=getTheNumberOfFix(prefix,n);    //  获取当前的前缀下的所有的数字的个数
            if(numberOFcurrentFix+currentPosition>k)                //  说明 k 在当前的前缀下 
            {
                prefix *=10;
                currentPosition++;
            }else                                                   // k 在下一个前缀
            {
                prefix++;
                currentPosition +=numberOFcurrentFix;
            }
        }
        return prefix;
    }
};

给定整数 n 和 k,找到 1 到 n 中字典序第 k 小的数字。

注意:1 ≤ k ≤ n ≤ 109。

示例 :

输入:
n: 13   k: 2
 
输出:
10
 
解释:
字典序的排列是 [1, 10, 11, 12, 13, 2, 3, 4, 5, 6, 7, 8, 9],所以第二小的数字是 10。

思路:

如果和之前的一个题(https://blog.csdn.net/qq_41864967/article/details/88375912)一样,dfs找出所有顺序,再去选择第k小的,由于数据较大,则会超时

数字n,按字典排序,找出第k小的数字

可以从1到100,慢慢地将数字一个一个地插入到它们该插入的地方,以此来查找其规律之所在。
     * 从1开始,1,2,3,4,5,6,7,8,9
     * 从10开始,1,10…19,2,3,4,5,6,7,8,9 
     * 从20开始,1,10…19,2,20…29,3,4,5,6,7,8,9
     * 以此类推,所有的10位数,都插入到与他们十位数位置上相等的个位数后面。
     * 观察字典顺序的数组,我们可以发现,其实这是个十叉树Denary Tree,就是每个节点的子节点可以有十个,比如数字1的子节点就是10到19,
     * 数字10的子节点可以是100到109,但是由于n大小的限制,构成的并不是一个满十叉树。我们分析题目中给的例子可以知道,数字1的子节点
     * 有4个(10,11,12,13),而后面的数字2到9都没有子节点,那么这道题实际上就变成了一个先序遍历十叉树的问题,那么难点就变成了如何计
     * 算出每个节点的子节点的个数
,我们不停的用k减去子节点的个数,当k减到0的时候,当前位置的数字即为所求
       如数字1和数字2,我们要求按字典遍历顺序从1到2需要经过多少个数字,首先把1本身这一个数字加到step中,然后我们把范围扩大十倍,范围
       变成10到20之前,但是由于我们要考虑n的大小,由于n为13,所以只有4个子节点,这样我们就知道从数字1遍历到数字2需要经过5个数字,然后
       我们看step是否小于等于k,如果是,我们cur自增1,k减去step;如果不是,说明要求的数字在子节点中,我们此时cur乘以10,k自减1,以此
       类推,直到k为0推出循环,此时cur即为所求。

    public int findKthNumber(int n, int k) {   //13 2
        int cur = 1;
        k = k - 1;
        while (k > 0) {   //2
            int steps = getSteps(n, cur, cur + 1);  //5
            if (steps <= k) {
                cur += 1;           
                k -= steps;         
            } else {
                cur *= 10;          //10
                k -= 1;             //0
            }
        }
        return cur;
    }
 
    //1, 10, 11, 12, 13, 2, 3, 4, 5, 6, 7, 8, 9
    /*           sum
     * 1  2   1  1
     * 10 20  4  5
     */
    public int getSteps(int n, long n1, long n2) {
        int steps = 0;
        while (n1 <= n) {
            steps += Math.min(n + 1, n2) - n1;
            n1 *= 10;
            n2 *= 10;
        }
        return steps;
    }

相关文章: