剑指Offer----面试题32:从1到n整数中1出现的次数

本文介绍两种计算从1到N的整数中数字1出现次数的方法。第一种方法通过遍历所有数字并检查每一位是否为1,时间复杂度较高。第二种方法利用数字规律进行分段计算,采用递归思想降低时间复杂度。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

题目:


输入一个整数n,求从1到n这n个整数的十进制表示中1出现的次数。例如输入12,从1到12这些整数中包含1的数字有1,10,11,12,也就是说1出现了5次。


方法一:不考虑时间复杂度的解法


分析:


循环遍历从1到n的每一个数字,累加每一个数字中所包含的1的个数,如果输入数字n,n有O(logn)位,那么时间复杂度就是O(n*longn)。

源代码实现:

#include<iostream>

using namespace std;

int ComputeNum1(int num)
{
	int Cnum = 0;
	
	for (int i = 1; i <= num;  ++i)
	{
		int temp = i;
		while (temp)
		{
			int yushu = temp % 10;
			if (yushu == 1)
				++Cnum;
			temp /= 10;
		}
	}

	return Cnum;
}

void test1()
{
	cout << "===============test1:输入边界值0==============" << endl;
	cout << ComputeNum1(0) << endl;
	cout << endl;
}

void test2()
{
	cout << "===============test2:输入边界值1==============" << endl;
	cout << ComputeNum1(1) << endl;
	cout << endl;
}

void test3()
{
	cout << "===============test3:输入负值-12==============" << endl;
	cout << ComputeNum1(-12) << endl;
	cout << endl;
}

void test4()
{
	cout << "===============test4:输入正常值12==============" << endl;
	cout << ComputeNum1(12) << endl;
	cout << endl;
}

int main()
{
	test1();
	test2();
	test3();
	test4();

	system("pause");
	return 0;
}

运行结果:
===============test1:输入边界值0==============
0

===============test2:输入边界值1==============
1

===============test3:输入负值-12==============
0

===============test4:输入正常值12==============
5

请按任意键继续. . .


方法二:从数字规律着手


分析:


例如输入数字21345,我们将之分为两段1~1345和1346~21345。

首先看1346~21345中1出现的次数。这时候又分为两种情况:
  • 首先是1出现在最高位(万位)的情况,10000~19999,共出现了10000次,这时需要注意的是,并不是对所有的5位数而言在万位出现的次数都是10000次,对于万位是1的数字如12345,1出现在万位的次数是2346次,也就是出去最高位的数字加上1;
  • 其次是1出现在最高位之外的其他四位数中的情况。例子中1345~21345中数字中后4为中1出现的次数是2000次。由于最高位为2,可以再分为两段1346~11325和11346~21345,每一段剩下的4位数字中,选择其中一位为1,其他三位从十位数字中随机选择,相当于全排列,因此有2*10*10*10 = 2000次。
其次是1~1345,使用递归的思路。

源代码:

#define _CRT_SECURE_NO_WARNINGS

#include<iostream>
#include<cstdio>

using namespace std;

int NumberOf1(const char *strN);
int PowerBase10(unsigned int n);

int NumberOf1Between1AndN(int n)
{
	if (n <= 0)
		return 0;

	char strN[50];
	sprintf(strN, "%d", n);
	return NumberOf1(strN);
}

int NumberOf1(const char *strN)
{
	if (strN == nullptr || *strN<'0' || *strN>'9' || *strN == '\0')
		return 0;

	int first = *strN - '0';
	unsigned int length = static_cast<unsigned int>(strlen(strN));

	if (length == 1 && first == 0)
		return 0;

	if (length == 1 && first > 0)
		return 1;

	//假设strN是“21345”
	//numFirstDigit是数字10000~19999的第一个位中的数目
	int numFirstDigit = 0;
	if (first > 1)
		numFirstDigit = PowerBase10(length - 1);
	else if (first == 1)
		numFirstDigit = atoi(strN + 1) + 1;

	//numOtherDigits是1346~21345除了第一位之外的数位中的数目
	int numOtherDigits = first*(length - 1)*PowerBase10(length - 2);
	//numberRecursive是1~1345中的数目
	int numRecursive = NumberOf1(strN + 1);

	return numFirstDigit + numOtherDigits + numRecursive;
}


int PowerBase10(unsigned int n)
{
	int result = 1;
	for (unsigned int i = 0; i < n; ++i)
		result *= 10;

	return result;
}


void test11()
{
	cout << "===============test1:输入边界值0==============" << endl;
	cout << NumberOf1Between1AndN(0) << endl;
	cout << endl;
}

void test12()
{
	cout << "===============test2:输入边界值1==============" << endl;
	cout << NumberOf1Between1AndN(1) << endl;
	cout << endl;
}

void test13()
{
	cout << "===============test3:输入负值-12==============" << endl;
	cout << NumberOf1Between1AndN(-12) << endl;
	cout << endl;
}

void test14()
{
	cout << "===============test4:输入正常值12==============" << endl;
	cout << NumberOf1Between1AndN(21345) << endl;
	cout << endl;
}

int main()
{
	test11();
	test12();
	test13();
	test14();

	system("pause");
	return 0;
}

运行结果:
===============test1:输入边界值0==============
0

===============test2:输入边界值1==============
1

===============test3:输入负值-12==============
0

===============test4:输入正常值21345==============
18821

请按任意键继续. . .







评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值