面试题17:打印从1到到最大的n位数
题目:
输入数字 n,按顺序打印出从 1 到最大的 n 位十进制数。比如输入 3,则打印出 1、2、3 一直到最大的 3 位数 999。
示例 1:
输入: n = 1
输出: [1,2,3,4,5,6,7,8,9]
说明:
用返回一个整数列表来代替打印
n 为正整数
题解:
大数越界问题:
当 n较大时,end=10^n - 1会超出 int32 整型的取值范围,超出取值范围的数字无法正常存储。但由于本题要求返回 int 类型数组
(如下第一个代码,返回vector<int>,即,数组中每一个元素都是int型
),相当于默认规定了n的范围,给出n后,产生的所有数字都在 int32 整型取值范围内,因此不需要考虑大数越界问题,只需定义区间 [1, 10^n - 1]和步长 1 ,通过 for循环生成结果列表 res并返回即可。
class Solution {
public:
vector<int> printNumbers(int n) {
}
};
若本次不是用“一个整数列表来代替打印”,而是“直接打印出,且返回void类型”,就必须考虑大数问题
。
注:
为练习大数问题,不管返回 int 类型数组还是void类型,都考虑大数解法。
一、简单解法:(不存在大数问题,这种方法可以)
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;
}
};
二、大数解法:(虽说本题返回int 类型数组,不存在大数问题)
在这一题中,由于返回的是一个 int 型的数组,所以是不可能超过INT_MAX的,但是一般大数问题都不会要求返回 int 数组来保存每一位数,而是循环输出每一位数
。
题解:
1、首先需要表达大数
,常用的方法是:用字符串或数组表达大数。(这里用字符串)
2、将字符串的每一位初始化为'0'(字符)
,然后每一次为字符串表示的数字加1,再返回数组或打印出来。这里主要包括两步:
——2.1在字符串表达的数字上模拟加法
;
——2.2将字符串表达的数字以传统阅读的方式返回数组;
3、如何在每一次增加1后,快速判断是不是到了最大的n位数:
第一个字符产生进位时(即,最高位产生进位时)。
例如:n=3,只有当最大3位数999加1时,最高位才会产生进位。
知识点扩展:char与int之间的加减法(比如 1 + ‘0’ 和 ‘1’ - ‘0’)
首先我们看一下ASCII表,会发现红框部分是字符型型[0, 9]的ASCII值[48, 57],它们是连在一起的。
一个字符在内存中存放形式是以它的ASCII码形式存放的,所以
1、char与int相加,得到一个char。比如:‘0’ + 1 = 48 + 1 = 49 = ‘1’。
2、char减去char,得到一个int。比如:‘2’ - ‘0’ = 50 - 48 = 2。
所以,如果我们想把一个int型的数(比如 i = 5)转成char型
,我们可以写成:char c = i + '0'
;。
如果我们想把一个char型的数(比如 c = '5')转成int型
,我们可以写成:int i = c - '0'
。
【不管是int转换成char,还是cha转换成int,都需要'0'
来辅助】
代码:
class Solution {
public:
vector<int> output;
vector<int> printNumbers(int n) {
// 以下注释的前提:假设 n = 3
if(n <= 0) return vector<int>(0);
string s(n, '0'); // s最大会等于999,即s的长度为n
while(!overflow(s)) inputNumbers(s);
return output;
}
bool overflow(string& s)
{
// 本函数用于模拟数字的累加过程,并判断是否越界(即 999 + 1 = 1000,就是越界情况)
//每次都只执行一次加1操作(即,if(i == s.length() - 1) current ++;),余下的循环是在处理个位加1后的进位操作;
bool isOverFlow = false;
int carry = 0; // carry表示进位
for(int i=s.length()-1; i>=0; --i)
{
//s[i] - '0' :将s[i]对应的char转换成int型
int current = s[i] - '0' + carry; // current表示执行新一轮操作之前s[i]的值
if(i == s.length() - 1) current ++; // 如果i此时在个位,current执行 +1 操作; 加1操作只发生在个位。
if(current >= 10)
{
// 假如i已经在最大的那一位了,而current++之后>=10,说明循环到头了,即999 + 1 = 1000
if(i == 0) isOverFlow = true;
else
{
// 只是普通进位,比如current从9变成10
carry = 1;
s[i] = current - 10 + '0';
}
}
else
{
// 如果没有进位,更新s[i]的值,然后直接跳出循环,这样就可以回去执行inputNumbers函数了,即往output里添加元素
s[i] = current + '0';//current + '0' :将int型转换成char
break;
}
}
return isOverFlow;
}
void inputNumbers(string s)
{
// 本函数用于循环往output中添加符合传统阅读习惯的元素。比如001,我们会添加1而不是001。
bool isUnwantedZero = true; // 判断是否是不需要添加的0,比如001前面的两个0
string temp = "";
for(int i=0; i<s.length(); ++i)
{
if(isUnwantedZero && s[i] != '0') isUnwantedZero = false;
if(!isUnwantedZero) temp += s[i];
}
output.push_back(stoi(temp));//stoi:string to int把数字字符串转换成int输出
}
};
知识点:
1、
stoi:参考
2、
解决大数问题的常用方法就是:
把数字转换成字符串。