剑指offer----C语言版----第十二天

目录

打印从1到最大的n位数

1.1 题目描述

1.2 Leetcode上的解题思路

1.3 考虑大数的问题

1.3.1 使用字符串模拟数字的加法

1.3.2 使用全排


打印从1到最大的n位数

原题链接:
剑指 Offer 17. 打印从1到最大的n位数 - 力扣(LeetCode)

1.1 题目描述

输入数字 n,按顺序打印出从 1 到最大的 n 位十进制数。比如输入 3,则打印出 1、2、3 一直到最大的 3 位数 999。

1.2 Leetcode上的解题思路

这道题目看起来很简单。我们看到这个问题之后,最容易想到的办法是先求出最大的n 位数,然后从1到最大的数赋值到数组中的对应下标即可。最大值怎么算嘞:假设输入3, 最大的三位数999 = 10 ^ 3 - 1。好的,代码如下:

int* printNumbers(int n, int* returnSize){
    int max = (int)pow(10, n) - 1;
    *returnSize = max;
    int*arr = (int*)malloc(sizeof(int)*max);
    int i;
    for(i = 0; i < max; i++)
    {
        arr[i] = i+1;
    }
    return arr;
}

1.3 考虑大数的问题

leetcode上返回的是一个int类型的数组,显然默认了数值并不会超过int类型的存储范围。但是既然是剑指offer上的题,肯定是不可能不考虑大数的问题滴。由于leetcode上无法进行测试,我就在Visual Studio 上演示结果哈!!!

1.3.1 使用字符串模拟数字的加法

在用字符串表示数字的时候,最直观的方法就是字符串里每个字符都是0'~"9"之间的某一个字符,用来表示数字中的一位。因为数字最大是n 位的,因此我们需要一个长度为n+1 的字符串(字符串中最后一位是结束符号"0')。当实际数字不够n 位的时候,在字符串的前半部分补0。首先把字符串中的每一个数字都初始化为0,然后每一次为字符串表示的数字加1,再打印出来。因此,我们只需要做两件事:

一是在字符串表达的数字上模拟加法;

二是把字符串表达的数字打印出来。

(1):在字符串表达的数字上模拟加法:(假设字符串的开头存高位)

我们需要知道什么时候停止在number上增加1,即什么时候到了最大的n 位数“999..99”(n 个9)。一个最简单的办法是在每次递增之后,都调用库函数 strcmp比较表示数字的字符串number和最大的n 位数“999..99”,如果相等则表示已经到了最大的n 位数并终止递增。虽然调用strcmp 很简单,但对于长度为 n 的字符串,它的时间复杂度为 O(n)。

我们注意到只有对“999...99”加1 的时候,才会在第一个字符(下标为0)的基础上产生进位,而其他所有情况都不会在第一个字符上产生进位。因此,当我们发现在加1 时第一个字符产生了进位,则已经是最大的n 位数,此时我们终止循环即可。

具体方法如下:

(2):将字符串表达的数字打印出来

接下来我们再考虑如何打印用字符串表示的数字。虽然库函数 printf可以很方便地打印出一个字符串,但在本题中调用 printf并不是最合适的解决方案。前面我们提到,当数字不够n位的时候,在数字的前面补0,打印的时候这些补位的0不应该打印出来。比如输入3 的时候,数字 98 用字符串表示成“098”。如果直接打印出 098,就不符合我们的阅读习惯。为此,我们定义了函数 printStr,在这个函数里,只有在碰到第一个非0 的字符之后才开始打印,直至字符串的结尾。

bool isOverflow(char* str, int n)
{
    //假设没有超过
    bool overflow = false;
    //是否进位
    int carry = 0;
    for (int i = n - 1; i >= 0; i--)
    {
        //将字符转化为数字
        int num = str[i] - '0' + carry;
        //个位加1
        if (i == n - 1)
        {
            num++;
        }
        //判断各位加1是否大于9
        if (num > 9)
        {
            //加1产生进位
            //判断是哪一位产生进位
            if (i == 0)
            {
                //如果下标为0的数字产生了进位,改变overflow
                overflow = true;
            }
            else
            {
                //不是下标为0的数字产生进位
                num -= 10; 
                carry = 1; //进位1
                str[i] = '0' + num; //将数字转回字符
            }

        }
        else
        {
            //加1没产生进位
            str[i] = '0' + num; //将数字转为字符
            //加一完成后退出循环
            break;
        }

    }
    //返回判断结果
    return overflow;

}


void printStr(char* str)
{
    //如果指向了不为0的字符改为true,就可以继续打印啦
    bool begin = false;
    char* temp = str;
    while (*temp)
    {
        if (*temp != '0' || begin)
        {
            begin = true;
            printf("%c", *temp);
        }
        temp++;
    }
    printf("\t");
}

void printNumbers(int n)
{
    //非法输入
    if (n <= 0)
    {
        return;
    }
    //开辟字符串存放数字,大小为n+1个char
    char* str = (char*)malloc(sizeof(char) * (n + 1));
    //将字符串初始化为字符0
    memset(str, '0', n);
    //添加字符串的结束字符\0
    str[n] = '\0';

    //循环打印, 当没有超出最大的n位数继续打印
    while (!isOverflow(str, n))
    {
        printStr(str);
    }
    free(str);
    str = NULL;
}

int main()
{

    printNumbers(2);
    return 0;
}

1.3.2 使用全排

上述思路虽然比较直观,但由于模拟了整数的加法,代码有点长。接下来我们换一种思路来考虑这个问题。如 果我们在数字前面补0,就会发现n位所有十进制数其实就是n个从0到n个9 的全排列。也就是说,我们把数字的每一位都从 0 到9 排列一遍,就得到 了所有的十进制数。只是在打印的时候,排在前面的0不打印出来罢了。

全排列用递归很容易表达,数字的每一位都可能是0~9 中的一个数, 然后设置下一位。递归结束的条件是我们已经设置了数字的最后一位。

此递归函数的作用是,首先排列最高位(最高位依然下标为0),排列最高位的时候递归进到下标为1的位置进行排列,进到下标为1的位置时,同样递归到下标为2的位置进行排列,以此类推直到满足递归结束的条件,递去归来的过程不理解还是画图哈!这道题还是比较简单的。

void printStr(char* str)
{
    //如果指向了不为0的字符改为true,就可以继续打印啦
    bool begin = false;
    char* temp = str;
    while (*temp)
    {
        if (*temp != '0' || begin)
        {
            begin = true;
            printf("%c", *temp);
        }
        temp++;
    }
    printf("\t");
}


void recurison(char* str, int n, int index)
{
    //如果数字的排到了最高位,则结束递归
    if (index == n - 1)
    {
        //打印此次排列好的数字
        printStr(str);
        return;
    }

    for (int i = 0; i < 10; i++)
    {
        //对输入的index的下一位进行1-9的全排列
        str[index + 1] = i + '0';
        //递归到下一位
        recurison(str, n, index + 1);
    }
}

void printNumbers(int n)
{
    //非法输入
    if (n <= 0)
    {
        return;
    }
    //开辟字符串存放数字,大小为n+1个char
    char* str = (char*)malloc(sizeof(char) * (n + 1));

    //添加字符串的结束字符\0
    str[n] = '\0';

    for (int i = 0; i < 10; i++)
    {
        //将数字转化为字符
        str[0] = i + '0';
        //进入递归尝试递归下一位
        recurison(str, n, 0);
    }

    free(str);
    str = NULL;
}


int main()
{

    printNumbers(3);
    return 0;
}

  • 15
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 11
    评论
评论 11
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

姬如祎

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值