字典序的第K小数字

312 阅读2分钟

一起养成写作习惯!这是我参与「掘金日新计划 · 4 月更文挑战」的第25天,点击查看活动详情

前言

笔者除了大学时期选修过《算法设计与分析》和《数据结构》还是浑浑噩噩度过的(当时觉得和编程没多大关系),其他时间对算法接触也比较少,但是随着开发时间变长对一些底层代码/处理机制有所接触越发觉得算法的重要性,所以决定开始系统的学习(主要是刷力扣上的题目)和整理,也希望还没开始学习的人尽早开始。

系列文章收录《算法》专栏中。

力扣题目链接

问题描述

给定整数 n 和 k,返回  [1, n] 中字典序第 k 小的数字。

示例 1:

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

示例 2:

输入: n = 1, k = 1
输出: 1

提示:

  • 1 <= k <= n <= 10^9

剖析

image.png 关于字典序,看这个图就好了,可以利用十叉树的结构推算出避免一个一个遍历,跳过一个节点就需要跳过该节点及其所有子节点,如果k大于该节点及其子节点的话就换相邻树。很多时候并不需要构建树,而是利用树的结构。具体还是看代码吧。

代码

package com.study.algorithm.sort;

public class FindKthNumber {
    public static int findKthNumber(int n, int k) {
        int curr = 1;
        k--;
        //当k等于0就说明不需要查找了,当前值就是需要的值
        while (k > 0) {
            //最后返回以当前节点为跟节点包含的节点个数,如果是第一次的话那就是从第一小到第steps小的个数
            int steps = getSteps(curr, n);
            //如果k大于等于这个个数的话就需要继续往右遍历这一层,不需要往下走
            if (steps <= k) {
                //减去steps代表从右边从新开始
                k -= steps;
                //往右遍历,curr当然需要++
                curr++;
            } else {//如果k小于steps说明在steos范围内,就需要往下检查
                //往下就需要*10
                curr = curr * 10;
                //往下需要--,跳过了一个节点
                k--;
            }
        }
        return curr;
    }

    public static int getSteps(int curr, long n) {
        int steps = 0;
        long first = curr;
        long last = curr;
        //只有右边小于等于才能统计steps
        while (first <= n) {
            //可能不是完全平衡树
            steps += Math.min(last, n) - first + 1;
            //最前面的节点:继续往下*10
            first = first * 10;
            //最后面的节点:继续往下*10+9
            last = last * 10 + 9;
        }
        //最后返回以当前节点为跟节点包含的节点个数
        return steps;
    }

    public static void main(String[] args) {
        System.out.println(findKthNumber(1300, 15));
    }
}