剑指offer-17 打印n位10进制数

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

n的位数可能比long的存储范围还大,最后打印字符串即可。
为了加大效率,不采用BigInteger类。

文章目录


前言

这题不知道为什么力扣的题目整个的削弱了《据说是因为lc恰烂钱》。
因为正好不理解java对大数的处理,记录一下真正的原题题解。

解法

思路:

如果不使用普通的int和long而使用String来存储,就不能方便的使用加号或者自动进位;
另外,若是考虑实现String字符串的进位也是不合理的,因为这会带来大量的字符串拼接操作,效率超级低。

换种思路,任何n进制数不过是n位0~9数字的组合,可以考虑通过对这些字符的排列来输出。
问题是,当前几位为0时,需要将0全部去掉。这个先不考虑,先实现组合问题。

考虑到,没固定1位,下一位就会从随机选一个数字,可以考虑通过dfs的思想,这是一个不涉及回溯的全深度搜索。
同时,为了增加效率,使用StringBuilder类来增加拼接效率。


    StringBuilder sb = null;
    char[] temp = null, pool = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9'};
    int n = 0;
    public String printNumbers(int n) {
        if (n <= 0) return null;
        this.n = n;
        sb = new StringBuilder();
        temp = new char[n];
        dfs(0);
        //删除最后一位的逗号
        sb.deleteCharAt(sb.length() - 2);
        return sb.toString();
    }

    //k暂存当前已经处理的位数
    public void dfs(int k) {
        if (k == this.n) {
            sb.append(String.valueOf(temp));
            sb.append(", ");
            return;
        }

        for (int i = 0; i < pool.length; i++) {
            temp[k] = pool[i];
            dfs(k + 1);
        }
    }


根据题意,要求数字从1开始,且从非零位开始打印。

参考大佬的思想:

设置一个在字符数组temp中起始位置,其代表当前“数字”的第一个非零元素,记为start。
可见,start越小,代表的数字越大。
如,当n=3时, 从1~9, start为2, 从10-99, start为1,。。。

另外,每当发生进位时,start会减一,即start后面的数字全为9时。
假设start后面的数字9的数目为nine,则start + nine == n,时,会发生进位
如上面的n=3时, 若nine = 2,start = 1, 即说明现在是二位数,且值为99,故应该进一位。
进一位即将start–即可。
【难以理解的是放这个判断的位置:
放在循环中肯定是不行的,因为会导致分支污染多次start–,最后甚至到了负数;
放在for循环前面会导致多执行一次循环。
放在方法最后,或者开头的结束条件时,都可以保证正常执行】

同时,为了避免分支污染,需要在回溯时将nine值返还
【比如说,现在n==3,程序在执行k == 3的时候的遍历,此时,对于三位中的for循环,必然会去更低位2位进行递归,对于2中某一个时候数位假设为j的情况,也会去更低位1进行遍历,这时候,若是1数位遍历到’9’时,nine就会加一,若j数位的1数位全部遍历完毕,轮到j+1数位,作为全局变量的nine如果不返还,就会使得j+1的判断中平白多了一个nine,会导致nine + start == n的判断失效】

另外,为了满足题目中返回值为int[], 需要在转化字符串时将其parseInt(), 并且为了让数值从1开始,也不应该记录0.【注,真正面试的时候应该不要parseInt(),否则就不是大数问题了,还是应该考虑到用StringBuilder去添加字符串“数字”

int[] result = null;
    int n, start, nine, index = 0;
    char[] num, pool = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9'};
    public int[] printNumbers(int n) {
        if (n <= 0) return null;
        result = new int[(int)Math.pow(10, n) - 1];
        num = new char[n];
        start = n - 1; //一位数时start为倒数第一位
        this.n = n;
        nine = 0;
        dfs(0);
        return result;
    }

    private void dfs(int k) {
        if (k == this.n) {
            int number = Integer.parseInt(String.valueOf(num).substring(start));
            if (number != 0) result[index++] = number;
            if (start == n - nine) start--;
            return;
        }

        for (int i = 0; i < pool.length; i++) {
            if (pool[i] == '9') nine++;
            num[k] = pool[i];

            dfs(k + 1);
        }

        nine--;
    }

附上一个快速幂的题目:
求double数字的n次幂;

思路:
将求幂次转化为求二进制的系数问题,可以让乘积次数少一半【由原来一次*x变成一次求x的平方】,将原本复杂度为O(n)的问题降为O(logn)。

任何数的二进制为:

n(b) = b0 * 2^0 + b1 * 2^1 + b2 * 2^2…
对系数b0,b1,b2…bi
取决于n的二进制的第i + 1数位是不是1.

因此:X^(n(b)) 在bi == 1的时候直接取平方即可。

上次文章写了对二进制为1的判断方法:
与1按位与,若为1,则最低位为1;
判断高位的方法只需要继续右移即可。

 public double myPow(double x, int n) {
        if (n == 0) return 1;
        if (x == 0) return 0;

        int k = n;
        if (k < 0) {
            k = -k;
        }
        double result = 1;

        while (k != 0) {
            if ((k & 1) == 1) result *= x;
            x *= x;
            k >>>= 1;
        }
        if (n > 0) return result;
        else return 1 / result;
    }

同样思路的递归实现:
考虑是否为偶数,按照

x^b = x^(b >>> 1) * x^(b >>> 1)【n为偶数】 或者 x^b = x^(b >>> 1) * x^(b >>> 1) * x 【n为奇数】来递归得到

 public double myPow(double x, int n) {
        if (n == 0) return 1;
        if (x == 0) return 0;

        if (n % 2 == 0) return pow(x, n);
        else return pow(x, n) * x;

    }

    public double pow(double x, int n) {
        if (n == 0) return 1;
        if (n == 1) return x;
        double temp = pow (x, n >>> 1);
        return temp * temp;
    }
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值