[剑指-Offer] 17. 打印从1到最大的n位数(高精度、全排列、代码优化、多方法)

1. 题目来源

链接:打印从1到最大的n位数
来源:LeetCode——《剑指-Offer》专项

2. 题目说明

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

示例 1:

输入: n = 1
输出: [1,2,3,4,5,6,7,8,9]

说明:

  • 用返回一个整数列表来代替打印
  • n 为正整数

3. 题目解析

3.1 方法一:pow()无脑解法

这个问题放到 LeetCode 上竟然不检查溢出问题,这应该是最大的败笔!

// 执行用时 :8 ms, 在所有 C++ 提交中击败了81.22%的用户
// 内存消耗 :13.7 MB, 在所有 C++ 提交中击败了100.00%的用户

class Solution {
public:
    vector<int> printNumbers(int n) {
        vector<int> res;
        int i = 1;
        int imax = pow(10, n);
        while(i < imax){
            res.push_back(i++);
        }
        return res;
    }
};

3.2 方法二:考虑大数char数组解法

仿照《剑指-Offer》中的解法,解决大数问题,注释详细。

参见代码如下:

// 执行用时 :12 ms, 在所有 C++ 提交中击败了51.29%的用户
// 内存消耗 :14.3 MB, 在所有 C++ 提交中击败了100.00%的用户

class Solution {
public:
	vector<int> res;
	vector<int> printNumbers(int n) {
		if (n <= 0)          
            return res;
		char* number = new char[n + 1];     // 创建一个能容纳最大值的字符数组,由于有一位要存储'\0',因此要开大一格
		memset(number, '0', n);             // 初始全部设置为0
		number[n] = '\0';                   // 第n位设为'\0'
		while (!Increment(number))
			PrintNumber(number);
		delete[]number;                     // 注意要释放内存
		return res;
	}
	bool Increment(char* number) {          // 形参传递char*指针
		bool isOverflow = false;            // 检测是否越界
		int nTakeOver = 0;                  // 存储进位
		int nLength = strlen(number);       // 长度为n,不是n+1
		for (int i = nLength - 1; i >= 0; i--) {
			int nSum = number[i] - '0' + nTakeOver;
			if (i == nLength - 1)           // 如果是第一位,进位
				nSum++;
			if (nSum >= 10) {               // 有进位
				if (i == 0)                 // 如果是最高位有进位,说明超过了给定得到最大值,越界
					isOverflow = true;
				else {                      // 非最高位有进位
					nTakeOver = 1;
					number[i] = nSum - 10 + '0';    // 对第i位进行设置
				}
			}
			else {                          // 没有进位,设置第i位数字,直接跳出循环
				number[i] = nSum + '0';
				break;
			}
		}
		return isOverflow;
	}
 
	void PrintNumber(char* number) {        // 形参传递char*指针,此处改变形参number指向的位置,不会使原始的number指针所指位置改变
		string s = "";
		bool isBegin0 = true;
		while (*number != '\0') {
			if (isBegin0 && *number != '0')     // 碰到'0',则不输出
                isBegin0 = false;
			if (!isBegin0)
				s += *number;
			number++;
		}
		int num = stoi(s);	// 转为整数,注意在此atoi函数不能给char*使用,仅能给const char*进行转换,故使用stoi
		res.push_back(num);
	}
};

3.3 方法三:string解法

思想与方法二一致,采用 string 实现而已。

参见代码如下:

// 执行用时 :144 ms, 在所有 C++ 提交中击败了5.77%的用户
// 内存消耗 :28.9 MB, 在所有 C++ 提交中击败了100.00%的用户

class Solution {
public:
	vector<int> res;
	vector<int> printNumbers(int n) {
		if (n <= 0) return res;
		string number(n, '0');                   // 创建一个能容纳最大值的字符数组,初始全部设置为0
		while (!Increment(number))
			saveNumber(number);
		return res;
	}
	bool Increment(string& number) {             // 注意要使用引用传递,否则无法修改number
		bool isOverflow = false;                 // 检测是否越界
		int nTakeOver = 0;                       // 存储进位
		int nLength = number.size();
		for (int i = nLength - 1; i >= 0; i--) {
		
			int nSum = number[i] - '0' + nTakeOver;
			if (i == nLength - 1)                // 如果是第一位,进位
				nSum++;
			if (nSum >= 10) {                    // 有进位
				if (i == 0)                      // 如果是最高位有进位,说明超过了给定得到最大值,越界
					isOverflow = true;
				else {
					nTakeOver = 1;
					number[i] = nSum - 10 + '0'; // 对第i位进行设置
				}
			}
			else {                               // 没有进位,设置第i位数字,并直接跳出循环
				number[i] = nSum + '0';
				break;
			}
		}
		return isOverflow;
	}
	void saveNumber(string number) {             //由于此处输出,不需要修改number,因此不需要加引用
		string s = "";
		bool isBegin0 = true;
		string::iterator it = number.begin();
		while (it != number.end()) {
			if (isBegin0 && *it != '0') 
                isBegin0 = false;
			if (!isBegin0)
				s += *it;
			it++;
		}
		int num = stoi(s);
		res.push_back(num);
	}
};

3.3 方法四:全排列递归解法

在《剑指-Offer》中还提到利用递归实现全排列的解法,也是相当简洁。在 n 位上实现 0~9 的全排列就搞定了,采用递归来解很简洁,代码已详细注释。

参见代码如下:

// 执行用时 :68 ms, 在所有 C++ 提交中击败了11.59%的用户
// 内存消耗 :14.3 MB, 在所有 C++ 提交中击败了100.00%的用户

class Solution
{
public:
	vector<int> res;
	vector<int> printNumbers(int n) {
		if (n <= 0) return res;
		string number(n, '0');
		for (int i = 0; i <= 9; i++) {              // 从高位到低位进行全排列
			number[0] = i + '0';                    // 首字符赋初值
			permutationNumbers(number, n, 1);       // 设置下一位
		}
		return res;
	}
	void permutationNumbers(string& number, int length, int index) {    // 对数字全排列
		if (index == length) {                   // 递归边界
			saveNumber(number);                  // 存储结果
			return;
		}
		else {
			for (int i = 0; i <= 9; i++)
			{
				number[index] = '0' + i;        // 设置第index位的字符
				permutationNumbers(number, length, index + 1);
			}
		}
	}
	void saveNumber(string number) {
		bool isBegin0 = true;
		string tempStr = "";
		string::iterator it = number.begin();
		while (it != number.end()) {            // 存储结果,正向查找第一个非零元素
			if (isBegin0 && *it != '0') 
                isBegin0 = false;
			if (!isBegin0) 
				tempStr += *it;
			it++;
		}
		if (tempStr != "") {                    // 从高位到低位全排列,要注意首字符为0时,tempStr为空,不能执行stoi
			int tempNum = stoi(tempStr);
			res.push_back(tempNum);
		}
	}
};
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

Ypuyu

如果帮助到你,可以请作者喝水~

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

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

打赏作者

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

抵扣说明:

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

余额充值