返回结果的字典序最小_字典序问题&leetcode 440 hard

之前有小伙伴面试就被问到了字典序

首先说一个基础的字典序:

题目1: 已知一个数字 m ,求仍有m所含数字组成的最小的数字。

比如当前数字是 34672 下一个比他大的怎么找呢?

我们应该让低位尽可能变大一点就好

不改变个位以上的数字,个位及以下能否变大?不能

不改变十位以上的数字,十位及以下能否变大?不能

不改变百位以上的数字,百位及以下能否变大?

6以下有比6大的数字,故可以变大

  • 找到6右面最小的比6大的数字,两个交换,变成了34762
  • 然后由于百位已经变大了,所以百位以下要尽可能小,才能使得变化最小所以要把 6 2 尽量变小->变为升序 26 就可以了。

思路总结:

从个位往高位扫描,找到第一个 高位<低位的位置

将高位换成他右边尽可能小的比他大的数字

将他右边变成 高位<=低位的排序

举例子: 2 5 7 6 5 4 3 2 1 -> 2 5 7 6 5 4 3 2 1-> 2 6 7 5 5 4 3 2 1-> 2 6 1 2 3 4 5 5 7

题目2: 字典序排列:

算法起点: 字典序最小排列 1- n eg: 1 2 3 4 5

算法终点: 字典序最大排列 n-1 eg: 5 4 3 2 1

算法执行过程 (like 题目1 )

  1. 找到排列中最右一个升序的首位置 x=ai(上面的5)
  2. 找到排列中第 i 位右边最后一个比 ai大的位置 y=aj
  3. 交换 x 和 y
  4. 将i+1位到最后部分反转

题目3: 从小到大打印 1到 n位所有数字

看起来觉着还可以? 但是直接操作会有坑:直接加法会导致溢出 if n很大

所以我们应该用字符串去模拟加法:

写法和递归全排列差不多。

    public void solve(int n){
        if(n<=0)
            return;
        String temp="";
        for(int i=0;i<10;i++){
            String p=temp;
            nextmax(n,p+String.valueOf(i),1);
        }

    }
    public void nextmax(int length,String temp,int index){
        //print
        if(index>=length){
            int k=0;
            while(k<temp.length()){
                if(temp.charAt(k)!='0') break;
                k++;}
            System.out.println(temp.substring(k));
            return;
        }
        //next
        for(int i=0;i<10;i++){
            String p=temp;
            nextmax(length,p+String.valueOf(i),index+1);
        }
    }

leetcode 440

f6ebed243407725d86558d3406357b7c.png

看到这个题我首先想到的是树,树的遍历,也想到了10叉树,但是我犯了一个错误,将root设为1 实际这里不能这样干,会失去你的树的可复制的性质,使得分类讨论变的比较麻烦。

看了lee215 大神的题解,感触颇深,最大的亮点在于观察上下界去计算每层的node数量,进而减少重复计算。我们可以建立一个字典树一样的结构。如图。

95449d5fc388d0dd6b0b6096d055ba27.png

我们最基本的思路就是遍历前缀树,每访问一个点就step++直到访问到需要的点。

我们可以先算出来这个数值在第一层哪个node集合里面

再去看对应第二层的哪个node集合

最后找到那个走了k步的node 就是我们的答案了。

下面说node集合数量怎么计算。

ab45147ce26556a3cbff49ea02b4a7ed.png

通过图我们会发现每层的node他的上界实际是下一个node对应层的最小值。所以计算该层node数量只需要 (curr+1)10-curr*10就好,但是他还有一个上界就是最大值。

故每层的数量就是 min((curr+1)*10,max_value+1)-curr*10;

计算这个node 有多少节点就把符合条件的每一层加起来就好

代码:

public int cal(int max,int n1,int n2){
    
    int step=0;
    while(n1<=max){
        step+=Math.min(n2,max+1)-n1;//当前层的node的数量
        n1*=10;//指针指向下一层的起点
        n2*=10;//指针指向下一层起点
    }
    return step;
    
}

整体代码

class Solution {
    public int findKthNumber(int n, int k) {
        
        int curr=1;
            k-=1;
        while(k>0){
            
            long step=calstep(n,curr,curr+1);
            
            if(k<step){//在这个范围内
                curr*=10;//移动一层
                k--;
            }else{//移动相邻的下一个节点
                k-=step;
                curr=curr+1;
            }
            
        }
        return curr;
    }
    public long calstep(int n,long n1,long n2){
        int ans=0;
        while(n1<=n){
            ans+=Math.min(n+1,n2)-n1;
            n1*=10;
            n2*=10;
        }
        return ans;
    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值