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;
}