Java&C++题解与拓展——leetcode440.字典序的第K小数字【字典序学习】

358 阅读2分钟

本文已参与「新人创作礼」活动,一起开启掘金创作之路。

每日一题做题记录,参考官方和三叶的题解

题目要求

在这里插入图片描述 【典型字越少事越大】

字典序

简单来讲就是把数字看成是英语单词一样排序,先按第一个字符排序,然后按第二个字符排序……长度短的靠前放。
也就是说靠前缀大小排序,这个排出来的顺序和十叉树的先序遍历是一样的。 在这里插入图片描述

思路

  • 将题目过程拆分为:确定前缀在前缀值下找目标值节点

  • 定义一个getCnt(x, limit)函数,用于计算[1,limit][1,limit]内以xx为前缀的数的个数。记xxlimitlimit的位数分别为xlenxlenllenllen

    • 位数lenlen小于llenllen的数均符合条件,直接计入,共10lenxlen10^{len-xlen}
    • 位数lenlen等于llenllen的数中,取limitlimit中长xlenxlen的前缀prepre
      • pre<xpre<x:所有xx为前缀的数都大于limitlimit
      • pre=xpre=x:[x00,limitx0\dots0, limit] 为符合条件的数,共limitx10llenxlen+1limit-x*10^{llen-xlen} +1个。
      • pre>xpre>x:所有xx为前缀的数都小于limitlimit,共10llenxlen10^{llen-xlen}个。
  • 从最小的前缀11开始枚举,枚举到前缀xx时,减去已经找到的符合条件的数,直至kk减为1得到目标值,记cnt=getCnt(x,limit)cnt=getCnt(x,limit)

    • cnt<kcnt<k:所有以xx为前缀的数都可以跳过,xx自增(跳到同一层隔壁兄弟节点),kk减去cntcnt,找下一个数值比x大的前缀;
    • cntkcnt \ge k:目标值前缀为xxx10x *10(跳到下一层),kk减去11,找下一个字典序比x大的前缀。

Java

在实现getCnt时,采用子串的方式构建prepre

class Solution {
    public int findKthNumber(int n, int k) {
        int res = 1;
        while (k > 1) {
            int cnt = getCnt(res, n);
            if(cnt < k) { //进入兄弟节点
                k -= cnt;
                res++;
            }
            else { //进入子树
                k--;
                res *= 10;
            }
        }
        return res;
    }

    //以x为前缀数的个数
    int getCnt(int x, int limit) {
        String xv = String.valueOf(x), lv = String.valueOf(limit);
        int xlen = xv.length(), llen = lv.length(), d = llen - xlen;
        int pre = Integer.parseInt(lv.substring(0, xlen)); //limit中与x一样长的前缀
        int cnt = 0;
        //长度小于llen的数
        for(int i = 0; i < d; i++)
            cnt += Math.pow(10, i);
        //长度为llen的数
        if(pre > x) //所有以x为前缀的数都满足
            cnt += Math.pow(10, d);
        else if(pre == x) //部分满足
            cnt += limit - x * Math.pow(10, d) + 1;
        return cnt;
    }
}
  • 时间复杂度:O(log2n)O(\log^2n),枚举前缀和getCnt的操作均复杂度均为O(logn)O(\log n)
  • 空间复杂度:O(log2n)O(\log^2n),忽略子串生成的复杂度为O(1)O(1)

C++

在实现getCnt时,采用树结构思路,找当前层小于limit的数,不断向下一层(前缀加长一位)寻找。

class Solution {
public:
    int findKthNumber(int n, int k) {
        int res = 1;
        while (k > 1) {
            int cnt = getCnt(res, n);
            if(cnt < k) { //进入兄弟节点
                k -= cnt;
                res++;
            }
            else { //进入子树
                k--;
                res *= 10;
            }
        }
        return res;
    }

    //以x为前缀数的个数
    int getCnt(int x, long limit) {
        int cnt = 0;
        long left = x, right = x; //当前层符合条件的最小(左)和最大(右)数
        while(left <= limit) {
            cnt += min(limit, right) - left + 1;
            left = left * 10;
            right = right * 10 + 9;
        }
        return cnt;
    }
};
  • 时间复杂度:O(log2n)O(\log^2 n)
  • 空间复杂度:O(1)O(1)

总结

读完要求是完全没有头绪的题目,解题思路不难,但要捋通顺还是花了点时间。
对于两种构建getCnt的方法,其底层逻辑相似,但一种利用字符串比较,另一种基于十叉树的层序遍历。



欢迎指正与讨论!