一、题目描述
输入数字n,按顺序打印从1到最大的n位数。比如输入3,则依次打印1、2、3一直到最大的3位数999.
二、一种跳入陷阱的解法
void printToMax(int n)
{
int number = 1;
int i = 0;
while (i++ < n)
number *= 10;
for (i = 1;i < number;i++)
cout << i << endl;
}
这种解法乍一看好似没什么问题,但是仔细分析可以发现题目中并没有给出n的范围,当这样的n位数超出计算机能表示整数的上限时,将会溢出而无法继续输出。换句话说,我们需要考虑大数问题。
三、字符串解决大数问题
最常用也是最容易的方法便是使用字符串或者数组来表示大数。接下来我们用字符串来解决上述问题。
用字符串表示很大的整数时,一个很直观的方法便是字符串的每一位都用‘0’~‘9’的字符来填充,用以表示大数的某一位。由于题目要求有n位数,那么我们需要的字符串长度为n+1(字符串最后一位是结束符‘\0’)。当实际数字不够n位时,前面的部分用‘0’填充。
这样转化以后,还有两个关键的问题需要解决。一是如何用字符串模拟整数的自增1运算,二是如何打印出来以符合我们日常书写整数的习惯(即数字前面的0不打印)。
要提前说明的一点:在C++中,字符的‘+’‘-’等运算实质上是ASC码进行相应运算。
先说说如何用字符串模拟整数的自增1运算:其实很简单,我们只需要从字符串的最后一位开始自增1(利用上述说明的一点即可),同时需要利用一个变量记录进位。那么何时自增结束呢(即达到最大的999……999,n个9):显然,仅当999……999(n个9)在其基础上自增1的时候,才会在字符串第一位产生进位。我们可以利用这个条件来判断自增结束,这样可以在O(1)的时间内进行判断。
接下来便考虑如何打印:其实有了字符串每一位初始化为‘0’的前提之后,我们打印时只需要从遇到的第一个非‘0’符开始打印即可。
void printToMax(int n)
{
if (n <= 0)
return;
char *number = new char[n + 1];
memset(number, '0', n);
number[n] = '\0';
while (!selfIncrement(number))
{
printNumber(number);
}
delete[]number;
}
bool selfIncrement(char *number)
{
bool isOverflow = false; //自增结束标志
int carray = 0; //进位标志
int length = strlen(number); //字符串长度
for (int i = length - 1;i >= 0;i--)
{
int iSum; //第i位自增后的值
iSum = number[i] - '0' + carray;
if (i == length - 1) //自增1运算
iSum++;
if (iSum >= 10) //产生进位
{
if (i == 0) //字符串第一位进位,则表示已达到最大n位数
isOverflow = true;
else
{
iSum -= 10;
carray = 1;
number[i] = '0' + iSum;
}
}
else //没有进位
{
number[i] = '0' + iSum;
break;
}
}
return isOverflow;
}
void printNumber(char *number)
{
bool isBeginningEqual0 = true;
int length = strlen(number);
for (int i = 0;i < length;i++)
{
if (isBeginningEqual0&&number[i] != '0')
isBeginningEqual0 = false;
if (!isBeginningEqual0)
cout << number[i];
}
cout << endl;
}
四、总结
上述思路直观、便捷的解决了题目中的大数问题。但是可以看见模拟字符串自增的函数具有相当的长度,这时候我们是否可以换一种思路来解决呢?
其实1~最大的n位数,就是n个‘0’~‘9’的全排列,我们可以通过递归生成全排列的方式来替换自增1操作。递归结束的条件是已经设置了字符串的最后一位。有兴趣的读者可以自己尝试实现。
通过上面的例子我们可以看到,很多时候题目没有给出整数范围的时候,我们就需要考虑大数问题,而用字符串来表示大数是一个简答、有效的方法。
注:以上内容为《剑指offer》学习笔记。