第32天:图解大数打印,这道题如此经典!
为大家分享一道经典面试题目。额外说明的一点是,这道题本身很简单,但是却可以作为很多 中等/困难 题目的基础,比如 超级次方,实现pow(x,n) 等等,在面试时需要额外小心。建议大家掌握!话不多说,直接看题。
01、题目示例
本题原始版本出自《剑指offer》,leetcode或许是因为自身原因,并没有很好的进行移植。当然,这道题本身也确实不太好移植,尤其是测试样例的构建,很容易把系统搞崩掉,所以一些测试样例处理成内存溢出,也是情有可原。
题目:大数打印 |
---|
输入数字 n,按顺序打印出从 1 到最大的 n 位十进制数。比如输入 3,则打印出 1、2、3 一直到最大的 3 位数 999。 |
示例 1:
输入: n = 1
输出: [1,2,3,4,5,6,7,8,9]
说明:
- 用返回一个整数列表来代替打印
- n 为正整数
02、简单解法
如果是第一次看到本题,应该是会想到???的解法。
直接通过 Math.pow 函数,计算出最大的 n 位十进制数,通过遍历求解。因为过于简单,所以直接上代码:
//java
class Solution {
public int[] printNumbers(int n) {
int len = (int) Math.pow(10, n);
int[] res = new int[len - 1];
for (int i = 1; i < len; i++) {
res[i - 1] = i;
}
return res;
}
}
补一个c++版本的:
//c++
class Solution {
public:
vector<int> printNumbers(int n) {
vector<int> res;
if (n == 0) return res;
//打印到数组中
for (int i=1,max=pow(10,n);i<max;i++)
{
res.push_back(i);
}
return res;
}
};
这种题解,应该不存在说有看不懂的。。。吧?(有的话,面壁思过!)
03、题目升级版
面试官说话了,“不允许使用math.pow,请手动实现一下”,“恶毒”的面试官发问了。
不让使用 math.pow , 那我们就不使用呗。根据上面的题解,我们已经把握到了关键,只要能找到 最大的 n 位十进制数,就可以解决问题。那我们就手动算一下:
//go
func printNumbers(n int) []int {
res := []int{}
l := 0
for 0 < n {
n--
l = l*10+9
}
for i := 1; i < l+1; i++ {
res = append(res, i)
}
return res
}
执行结果:
04、题目继续升级
面试官又说话了,“这道题目的名字叫做大数打印,如果阈值超出long类型,该怎么办呢?请手动实现一下!” 面试官总是可以想方设法为难咱们。(玩笑归玩笑,其实这个才是本题的核心)
到现在为止,本题才进入到关键环节。因为如果一个数很大,肯定没办法用单个变量类型进行表达。问题也发生了转化:如何使用其他的数据类型来模拟大数的表达?
这里先复习一下大数加法:在加法运算的时候假如有两个10000位数的两个数进行相加,那么用int、long、double型都装不下这么多位数,一般采用char数组来实现加法运算,解决精度的问题。说白了是啥意思,我们用 1234567 和 1234 来模拟一下:
- 取两个数位数大的一个作为数组长度
- 对两个数建立char数组,保存每一位上的值
- 对于位数小的数,高位补0。
- 同时创建sumArr,用来保存两数之和
- 考虑进位
当然,一般我们还使用一些比如 翻转存储计算 之类的技巧,这里就不说了,回头我会出一个单独的大数计算系列篇单独讲解。回到今天的题目。
对于本题,我们该如何模拟一个 “最大的n位十进制数” 呢?其实也是一样的,我们采用 char 数组进行存储。而我们每次递增1,相当于进行一次字符串相加的运算。但是这里额外要说明的一点是,我把题解恢复成了原题的要求:使用打印输出,而不是通过数组返回的形式。 毕竟返回数组的形式只是 leetcode 为了兼容平台测试而改编的。这里我就直接给出从剑指offer改编的题解了(JAVA):
//java
public void printNumbers(int n) {
//声明字符数组,用来存放一个大数
char[] number = new char[n];
Arrays.fill(number, '0');
while (!incrementNumber(number)) {
saveNumber(number); //存储数值
}
}
private boolean incrementNumber(char[] number) {
//循环体退出标识
boolean isBreak = false;
//进位标识
int carryFlag = 0;
int l = number.length;
for (int i = l - 1; i >= 0; i--) {
//取第i位的数字转化位int
int nSum = number[i] - '0' + carryFlag;
if (i == l - 1) {
//最低位加1
++nSum;
}
if (nSum >= 10) {
if (i == 0) {
isBreak = true;
} else {
//进位之后减10,并把进位标识设置为1
nSum -= 10;
carryFlag = 1;
number[i] = (char) ('0' + nSum);
}
} else {
number[i] = (char) (nSum + '0');
break;
}
}
return isBreak;
}
private void saveNumber(char[] number) {
boolean isBegin0 = true;
for (char c : number) {
if (isBegin0 && c != '0') {
isBegin0 = false;
}
if (!isBegin0) {
// 到这里并没有继续往下实现一个存储数组的版本,是因为原题其实就是要求打印数值。
// 这道题目在leetcode上被改动成返回int数组的形式,也只是为了测试方便,
// 本身leetcode并没有提供对应的大数测试样例,也是担心其内存溢出。
// 总之大家知道本题的考察点所在就可以了。
System.out.print(c);
}
}
System.out.println();
}
上面的代码强调两点:
- 对最低位 nSum 的值递增(也就是字符串加1运算),当大于等于10时,我们把进位标识改为1,同时恢复对 nSum 减10(29-31)
- 通过判断首位是否进位来判断到达最大的n位数情况。比如 n=4,只有对 9999 加 1,才会对第一个字符进位。
同样,我也实验了一下,如果我硬性的把代码改成数组的形式,然后在leetcode测试用例中构造 n = 10,就会出现这个:
所以建议大家是在IDE里练习,今天的题目到这里就结束了。
随意展示一张导图内容(所有的子节点都可以打开):
今日论点:
程序员们平时都喜欢逛什么论坛呢?
- Github:用 Pythone 高效的在抖音找小姐姐
- Leetcode:我只是不满足于现在的薪资
- Stack Overflow:不慌,这里有一个bug
- CodinGame:玩玩游戏而已
- GeeksforGeeks:学算法知识、盘他
- 小浩算法:冥冥中我们相遇
- Coursera:不要钱就对了
- B站:诗酒趁年华。
大家怎么看呢?评论区留下你的想法吧!
- 互联网+智能物流高峰论坛举行运的易现场签约完成战略布局
- Quartz.net通过配置文件来完成作业调度
- JavaScript 基础(一)
- 我也来说说.net开源
- 是时候对员工进行网络安全培训了:黑客正将目标瞄准打印机
- 进度条ProgressBar
- Microsoft Visual Studio International Pack
- 柯洁5冠在手“食言”再战AI:我已看开 输赢无所谓
- JGulp: 利用Gulp 配置的前端项目自动化工作流
- 微软Enterprise Library 4.0将支持依赖注入
- 时钟AnalogClock与DigitalClock
- 细数那些在2017年被黑客滥用的系统管理工具和协议
- Compass: 在你的应用中集成搜索功能
- 列表选择Spinner
- JavaScript 教程
- JavaScript 编辑工具
- JavaScript 与HTML
- JavaScript 与Java
- JavaScript 数据结构
- JavaScript 基本数据类型
- JavaScript 特殊数据类型
- JavaScript 运算符
- JavaScript typeof 运算符
- JavaScript 表达式
- JavaScript 类型转换
- JavaScript 基本语法
- JavaScript 注释
- Javascript 基本处理流程
- Javascript 选择结构
- Javascript if 语句
- Javascript if 语句的嵌套
- Javascript switch 语句
- Javascript 循环结构
- Javascript 循环结构实例
- Javascript 跳转语句
- Javascript 控制语句总结
- Javascript 函数介绍
- Javascript 函数的定义
- Javascript 函数调用
- Javascript 几种特殊的函数
- JavaScript 内置函数简介
- Javascript eval() 函数
- Javascript isFinite() 函数
- Javascript isNaN() 函数
- parseInt() 与 parseFloat()
- escape() 与 unescape()
- Javascript 字符串介绍
- Javascript length属性
- javascript 字符串函数
- Javascript 日期对象简介
- Javascript 日期对象用途
- Date 对象属性和方法
- Javascript 数组是什么
- Javascript 创建数组
- Javascript 数组赋值与取值
- Javascript 数组属性和方法
- Android串口通信封装之OkUSB的示例代码
- Android 中Activity 之间传递参数
- Android开发简单实现摇动动画的方法
- Android 中menu同时显示图标和文字的实现
- android基于SwipeRefreshLayout实现类QQ的侧滑删除
- PopupWindow自定义位置显示的实现代码
- Bootstrap 下拉菜单.dropdown的具体使用方法
- Android访问assets本地json文件的方法
- Android延时操作的三种方法
- 5步教你快速写一个android Router路由框架
- Android 中使用RecyclerView实现底部翻页
- Android中Glide库的使用小技巧总结
- Android Studio手动配置Gradle的方法
- Android仿微信@好友功能 输入@跳转、删除整块
- Android开发实现广告无限循环功能示例