【解题】打印1到最大的n位数——关于大数问题的探讨(C++实现)

一、题目描述

输入数字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》学习笔记。

你可能感兴趣的:(编程语言)