1.题目
输入数字n,按顺序打印出从1到最大的n位十进制数。比如输入3,则打印出1,2,3,则打印出1.2,3一直到最大的3位数999。
2.笔者解答
如果面试的时候真遇到这题,做梦都能笑醒吧,然后和笔者一样飞快的写下了下面代码。
void Print1ToMaxOfDigits_1(int n)
{
int number=1;
int i=0;
while(i++,n)
number*=10;
for(i=1;i<number<++i)
printf("%d\t",i);
}
事实证明,如果你笑醒了,那就洗洗再睡一次…
3.就提论题
初看之下好像没有问题,但如果仔细分析这个问题,我们就能注意到面试官没有规定n的范围。当输入的n很大的时候,我们求最大的n位数是不是用整型(int)或者长整型(long long)都会溢出?也就是说我们需要考虑大数问题。这是面试官在这道题里设置的一个大陷阱。
在字符串上模拟数字加法的解法,绕过陷阱才能拿到offer
经过前面的分析,我们很自然地想到解决这个问题需要表达一个大数。最常用也是最容易的方法是用字符串或者数组表达大数。接下来我们用字符串来解决大数问题。
在用字符串表示数字的时候,最直观的方法就是字符串里每个字符都是’0’~‘9’之间的某一个字符,用来表示数字中的一位。因为数字最大是n的,因此我们需要一个长度为n+1的字符串(字符串中最后一位是结束符号’\0’)。当实际数字不够n位时,在字符串的前半部分补0。
首先把字符串中的每一个数字都初始化为’0’,然后每一次为字符串表示的数字加1,再打印出来。因此,我们只需要做两件事:一是在字符串表达的数字上模拟加法:而是把字符串表达的数字打印出来。
基于上面的分析,我们可以写出如下代码:
void Print1ToMaxOfDigits(int n)
{
if(n<=0)
return ;
char *number=new char[n+1];
memset(number,'0',n);
number[n]='\0';
while(!Incremement(number))
{
PrintNumber(number);
}
delete []number;
}
在上面的代码中,函数Increment实现在表示数字的字符串number上增加1,而函数PrintNumber打印出number。这两个看似简单的函数都暗藏小小的玄机。
我们需要知道什么时候停止在number上增加1,即什么时候到了最大的n位数“9999…9”(n个9)。一个最简单的办法是在每次递增之后,都调用库函数strcmp比较表示数字的字符串number和最大的n位数“999…99”,如果相等则表示已经到了最大的n位数并终止递增。虽然调用strcmp很简单,但对于长度为n的字符串,它的时间复杂度为O(n)。
我们注意到只有对“999…99”加1的时候,才会在第一个字符(下标为0)的基础上产生进位,而其他所有情况都不会在第一个字符上产生进位。因此,当我们发现在加1时第一个字符产生了进位,则已经是最大的n位数,此时Increment返回true,因此函数Print1ToMaxOfNDigits中的while循环终止。如何在每一次增加1之后快速判断是不是到了最大的n位数是本题的一个小陷阱。下面是Increment函数的参考代码,它实现了用O(1)时间判断是不是已经到了最大的n位数。
bool Increment(char* number)
{
bool isOverflow=false;
int nTakeOver=0;
int nLength=strlen(number);
for(int i=nLenth-1;i>=0;i--)
{
int nSum=number[i]-'0'+nTakeOver;
if(i==nLength-1)
nSum++;
if(nSum>=10)
{
if(i==0)
isOverflow=true;
else
{
nSum-=10;
nTakeOver=1;
number[i]='0'+nSum;
}
}
else
{
number[i]='0'+nSum;
break;
}
}
return isOverflow;
}
接下来我们再考虑如何打印用字符串表示的数字。虽然库函数printf可以很方便地打印出一个字符串,但在本题中调用printf并不是最合适的解决方案。前面我们提到,当数字不够n位的时候,在数字的前面补0,打印的时候这些补位的0不应该打印出来。比如输入3的时候,数字98用字符串表示成“098”。如果直接打印098,就不符合我们的阅读习惯。为此,我们定义了函数PrintNumber,在这个函数里,只有在碰到第一个非0的字符之后才开始打印,直至字符串的结尾。能不能按照我们的阅读习惯打印数字是面试官设置的另外一个小陷阱。实现代码如下:
void PrintNumber(char* number)
{
bool isBeginning=true;
int nLength=strlen(number);
for(int i=0;i<nLength;++i)
{
if(isBeginning0&&number[i]!='0')
isBeginning0=false;
if(!isBeginning0)
{
printf("%c",number[i]);
}
}
printf("\t");
}
把问题转换成数字排列的解法,递归让代码更简洁
上述思路虽然比较直观,但由于模拟了整数的加法,代码有点长。要在面试短短几十分钟时间里完整、正确地写出这么长的代码,对很多应聘者而言不是一件容易的事情。接下来我们换一种思路来考虑这个问题。如果我们在数字前面补0,就会发现n位所有十进制数其实就是n个从0到9的全排列。也就是说,我们把数字的每一位都从0到9排列一遍,就得到了所有的十进制数。只是在打印的时候,排在前面的0不打印出来罢了。
全排列用递归很容易表达,数字的每一位都可能是0~9中的一个树,然后设置下一位。递归结束的条件是我们已经设置了数字的最后一位。
void Print1ToMaxOfNDigits(int n)
{
if(n<=0)
return ;
char* number=new char[n+1];
number[n]='\0';
for(int i=0;i<10;i++)
{
number[0]=i+'0';
Print1ToMaxOfNDigitsRecursively(number,n,0);
}
delete[] number;
}
void Print1ToMaxOfNDigitsRecursively(char* number,int length,int index)
{
if(index==length-1)
{
PrintNumber(number);
return ;
}
for(int i=0;i<10;i++)
{
number[index+1]=i+'0';
Print1ToMaxOfNDigitsRecursively(number,length,index+1);
}
}
本文探讨了在处理大数问题时,如何避免整型溢出,采用字符串模拟加法和利用数字排列的递归方法,高效打印从1到最大的n位十进制数。
1974

被折叠的 条评论
为什么被折叠?



