剑指Offer_#17_打印从1到最大的n位数

剑指Offer_#17_打印从1到最大的n位数

Contents

题目

输入数字 n,按顺序打印出从 1 到最大的 n 位十进制数。比如输入 3,则打印出 1、2、3 一直到最大的 3 位数 999。

示例 1:
输入: n = 1
输出: [1,2,3,4,5,6,7,8,9]
 
说明:
用返回一个整数列表来代替打印
n 为正整数

思路分析

leetcode上面这一题的题意与书本上的原意不符,书中这一题的考察重点是大数问题,即如果输入的数字位数n很大,表示的n位数字可能超出int的表示范围。但leetcode上面规定的返回值类型是int数组,那么相当于直接忽略了大数问题。

所以,在解答中,分为两个思路来做,

  • 第一种是直接不考虑大数问题,目标是能通过leetcode的测试;
  • 第二种是按照书上题意,还得考虑大数问题的存在。

思路1很简单,不详细写了。

对于思路2,其实也还分为两种方法:

  1. 字符'0'~'9'进行n位的全排列,即可得到所有的数字
  2. 用字符串表示数字,并模拟数字相加,进位的过程

方法1:字符全排列(考虑大数问题)

如下图所示,可以从最高位到最低位进行排列组合,将各种组合情况画成一个树的形式,那么打印所有数字的工作可以转化为对这棵树的深度优先遍历,每次遍历的路径,就是一个数字。
算法流程

  1. 开始时,start == 1.固定第1位数字为0,遍历第2位数字,即得到"00"-"09",去除前边的0,append进入res
  2. 遇到"09"时,nine更新为1,start更新为0
  3. "00"-"09"结束后,回溯到第一位数字,nine还原为0.固定第1位数字为1,遍历第二位数字,即得到"10"-"19",append进入res
  4. 遇到"19"时,nine更新为1
  5. "10"-"19"结束后,回溯到第一位数字,nine还原为0.固定第1位数字为2,遍历第二位数字,即得到"20"-"29",append进入res
    依次类推......
  6. 遇到"99"时,nine更新为2。循环结束,返回从1到99的字符串
    图解

方法2:用字符串表示数字,模拟数字加法

算法流程

  • 数字加1时,个位数增加1,如果个位数加1后大于10,则向更高位进位
  • 依次类推,从后往前遍历表示数字的字符串,在每一位数字上判断是否会进位(如果数字末尾是连续的9,那么会发生连续进位,比如199+1=200,会连续在个位和十位上进位)
    • 每次数字加1,完成所有进位操作之后,将本数字添加到res末尾
    • 添加数字之前,需要数字的第一个字符开始遍历,找到第一个非'0'字符的索引,去除数字前半部分补位的'0'
  • 如果最高位数字发生进位,超过10,那么说明已经到了n位数字能表示的最大值,添加数字到res的循环停止,返回res即可。

解答1:循环打印(不考虑大数问题)

class Solution {
    public int[] printNumbers(int n) {
        int max = (int)Math.pow(10,n);//注意这里必须进行类型转换,加上(int)       
        int[] res = new int[max - 1];
        for(int i = 1;i <= max - 1;i++){
            res[i - 1] = i;
        }
        return res;
    }
}

解答2:字符全排列(考虑大数问题)

public class Solution {
    StringBuilder res;//StringBuilder支持不断对字符串进行修改,且不会产生新的对象
    int nine = 0,start,n;//start是左边界
    char[] num, loop = {'0','1','2','3','4','5','6','7','8','9'};
    public String printNumbers(int n){
        this.n = n;//设置为类变量,递归函数也可以访问
        res = new StringBuilder();
        num = new char[n];//数组定义必须给出长度
        start = n - 1;//个位数,左边界为数字位数-1
        dfs(0);
        res.deleteCharAt(res.length() - 1);//删除最后一个逗号
        return res.toString();
    }
    void dfs(int x) {
        if (x == n) {
            String s = String.valueOf(num).substring(start);//将num的有效位取出(舍去开始的0)
            if(!s.equals("0")) res.append(s + ",");//舍去第一个“0”
            if(n - start == nine) start--;//nine是遍历过程中,遇到9的个数;start--说明进位了
            return;
        }
        for(char i:loop){
            if(i == '9') nine++;
            num[x] = i;//第x位数字,分别取值0~9
            dfs(x + 1);//第x位数字固定位0~9中任意一个,然后继续遍历x+1位数字
        }
        //第x位数字全部遍历结束,回溯到第函数dfs(x-1),之前nine++是因为在第x位遇到了9。
        //现在回到的x-1位,第x位遇到的9不算了,所以nine要恢复到原本的值,即nine--;
        nine--;
    }
}

注意: 此代码无法在leetcode上通过,因为这里返回的是字符串数组,而非leetcode要求的int数组,所以只能在本地IDE自行测试。

解答3:字符串表示数字(考虑大数问题)

通过字符串表示数字,模拟数字加法。
数字加法的要点就是逢10进位,进位操作从后向前。
如果第一位数字发生进位变为1,说明达到了n位数字表示的最大值。

public class Solution{
    ArrayList<String> res;
    char[] number;
    int n;//数字位数
    public ArrayList<String> print1ToMaxOfNDigits(int n){
        this.n = n;
        res = new ArrayList<String>();
        number = new char[n];
        for(int i = 0;i <= n - 1;i++){
            number[i] = '0';
        }
        while(!Increment()){
            AppendToRes();
        }
        return res;
    }

    public boolean Increment(){
        boolean isOverflow = false;
        int nTakeOver = 0;
        for(int i = n - 1;i >= 0;i --){//执行加一操作,分为进位和不进位分别处理
            int nSum = (int)(number[i] - '0') + nTakeOver;//本位数字加上进位的和
            if(i == n - 1) nSum ++;//数字加1,先加在个位数字上,其他位的变化都是进位导致的
            if(nSum >= 10){//nSum >= 10,说明发生进位了
                if(i == 0) isOverflow = true;//进位发生在最高位,说明已经到了最高位
                else{//进位发生在其他位,则进行进位操作
                    nSum -= 10;//本位数字减10
                    nTakeOver = 1;//记录进位,下轮循环开始时会加到下一位
                    number[i] = (char)('0' + nSum);
                }
            }else{//没有发生进位,那么就可以跳出循环了
                number[i] = (char)('0' + nSum);
                break;
            }
        }
        return isOverflow;
    }

    public void AppendToRes(){
        int notBegining0Index = 0;
        for(int i = 0;i <= n - 1;i ++){
            if(number[i] != '0'){
                notBegining0Index = i;
                break;
            }
        }
        String s = String.valueOf(number).substring(notBegining0Index);
        if(!s.equals("0")) res.add(s);//将当前数字添加到末尾
    }
}

注意: 此代码无法在leetcode上通过,因为这里返回的是字符串数组列表,而非leetcode要求的int数组,所以只能在本地IDE自行测试。

原文地址:https://www.cnblogs.com/Howfars/p/13177760.html