解题笔记(16)——几个数字的问题

        个人感觉,这类题的技巧性很巧,同时需要很好的分析能力,受到各大公司的青睐。下面列举几个,这些问题网上都能找到解答,自己实现了一下,供网友参考。

        问题1:整数的二进制表示中1的个数。

        思路:可以有以下几种解法。(1)使用除法操作。(2)使用位操作。(3)位操作的改进。(4)如果是8位的整数,可以直接用查表法或分支操作。《编程之美》上有详尽的介绍。这里给出(2)、(3)的解法。(3)的解法非常巧妙,不容易想到。

       参考代码:

//函数功能 : 整数的二进制表示中1的个数
//函数参数 : value为输入的整数
//返回值 :   1的个数
int CountOne_Solution1(int value)
{
	int counter = 0;
	unsigned flag = 1; //利用flag的方式,负数也可以处理了
	while(flag != 0)
	{
		if(value&flag) //检查二进制表示中的每一位
			counter++;
		flag = flag << 1;
	}
	return counter;
}
int CountOne_Solution2(int value)
{
	int counter = 0;
	while(value)
	{
		value &= (value - 1); //核心语句
		counter++;
	}
	return counter;
}

        问题2:输入一个整数n,求从1nn个整数的十进制表示中1出现的次数。例如输入12,从112这些整数中包含的数字有11011121一共出现了5次。

    思路:《编程之美》也有这道题目。最简单的是穷举法,我能想到的也就是穷举法。书上给出了另外一种高效的方法,需要很强的分析能力。其核心思想是统计“每一位上可能出现1的次数,然后把它们加起来”。具体分析过程为:(1)如果 n 是1位数,那么f(n) = 1。只有1这个数字出现了1。(2)如果 n 是2位数。个位上出现 1 的次数不仅与个位数字有关,与十位数字有关。同样十位上出现1的次数类似。规律为,如果十位数字等于1,那么十位上出现1的次数等于个位数字加1;如果十位数字大于1,那么十位上出现1的次数等于10。个位数字等于0,个位上出现1的次数等于十位数字;如果大于等于1,则为十位上的数字加1。

    看到这里,我有点晕了。真的要在短时间内作出这个分析,太难了。而且分析还没有结束,继续分析3位数的情况,然后得出一般的规律。这种问题没看过答案,很少有人能想出书上给出的高效算法。

    参考代码:

//函数功能 : 统计一个整数的十进制表示中1出现的次数
//函数参数 : n为整数
//返回值 :   1出现的次数
unsigned CountOneInAInteger(unsigned n)
{
	unsigned count = 0;
	while(n)
	{
		if(n % 10 == 1)
			count++;
		n /= 10;
	}
	return count;
}
//函数功能 : 统计从1到n这n个整数的十进制表示中1出现的次数
//函数参数 : n为整数
//返回值 :   1出现的次数
unsigned CountOneBitween1AndN_Solution1(unsigned n)
{
	unsigned count = 0;
	for(unsigned i = 1; i <= n; i++)
		count += CountOneInAInteger(i); //穷举法
	return count;
}
//优化的算法,很难,我只能看明白
unsigned CountOneBitween1AndN_Solution2(unsigned n)
{
	unsigned count = 0;
	unsigned factor = 1; //计算每一位上出现1的次数,从个位开始
	//每一位上出现1的次数与它的高位数字、低位数字、以及自身都有关系
	//比如12345,如果计算百位上出现1的次数,这要考虑高位数字12,以及低位数字45有关
	unsigned lowNum = 0;   //低位数字
	unsigned highNum = 0;  //高位数字
	unsigned curNum = 0;   //当前位
	while(n / factor != 0)
	{
		lowNum = n - (n / factor) * factor;
		highNum = n / (factor * 10);
		curNum = (n / factor) % 10;   
		switch(curNum)
		{
		case 0: //当前位的数字为 0
			count += highNum * factor; //只与高位有关 
			break;
		case 1:
			count += highNum * factor + lowNum + 1; //与高位低位都有关
			break;
		default:
			count += (highNum + 1) * factor; //与高位有关
			break;
		}
		factor *= 10; //计算下一位
	}
	return count;
}
    问题3:输入一个正数n,输出所有和为n连续正数序列。例如输入15,由于1+2+3+4+5=4+5+6=7+8=15,所以输出3个连续序列1-54-67-8

    思路:这道题简单一点,O(n)时间复杂度即可完成。用两个变量记录一段区间,low,high,Sum表示[low, high)所有数字之和。如果Sum = n,输出区间的值,然后Sum -= low,low++。如果Sum < n,那么Sum += high,high++。如果Sum > n,那么Sum-=low,low++。

    参考代码:

//函数功能 : 打印满足条件的连续序列
//函数参数 : begin为开始,end为结束,左闭右开区间
//返回值 :   无
void PrintSequence(unsigned begin, unsigned end)
{
	for(unsigned i = begin; i < end; i++)
		cout<<i<<' ';
	cout<<endl;
}
//函数功能 : 输出所有和为n连续正数序列
//函数参数 : n为整数
//返回值 :   无
void FindTargetSequence(unsigned n)
{
	if(n < 3)    //必须是2个连续数之和,n必须大于等于3
		return ;

	unsigned low = 1;
	unsigned high = 2;
	unsigned middle = n / 2;
	unsigned sum = 1;

	while(low <= middle) //只需考虑一半元素即可
	{
		if(sum == n)  //满足条件
			PrintSequence(low, high);

		if(sum < n)  //不够,说明还需要再加
		{
			sum += high;
			high++;
		}
		else //够了,说明要减一点
		{
			sum -= low;
			low++;
		}
	}
}

    问题4:调整数组顺序使奇数位于偶数前面(数组)。输入一个整数数组,调整数组中数字的顺序,使得所有奇数位于数组的前半部分。所有偶数位于数组的后半部分。要求时间复杂度为O(n)。

        思路:看到这个题目,第一反应就是快速排序的调整思想。快排调整是根据一个数与基准的大小关系,而这道题是根据一个数的奇偶性。代码实现差不多。

       参考代码:

bool IsOdd(int x)
{
	return (x & 1)? true: false;
}
//函数功能 : 调整数组顺序使奇数位于偶数前面
//函数参数 : pArray指向数组的指针,nLen为数组长度
//返回值 :   无
void PartionArray(int *pArray, int nLen, bool (*func)(int))
{
	int i = -1;
	for(int j = 0; j < nLen; j++)
	{
		if(func(pArray[j])) //满足调整条件
		{
			i++;
			int tmp = pArray[j];
			pArray[j] = pArray[i];
			pArray[i] = tmp;
		}
	}
}
          问题5:寻找丑数。我们把只包含因子2、3和5的数称作丑数(Ugly Number)。例如6、8都是丑数,但14不是,因为它包含因子7。习惯上我们把1当做是第一个丑数。求按从小到大的顺序的第1500个丑数。

          思路:最容易想到的就是从1开始,判断一个数是不是丑数,如果是将丑数个数加1,如果不是判断下一个数,直到丑数的个数为1500。

         参考代码:

//函数功能 : 判断一个数是不是丑数
//函数参数 : 被判断的数
//返回值 :   是否丑数
bool IsUglyNumber(int x)
{
	while(x % 2 == 0)
		x /= 2;
	while(x % 3 == 0)
		x /= 3;
	while(x % 5 == 0)
		x /= 5;

	return (x == 1)? true : false;
}
//函数功能 : 寻找丑数
//函数参数 : 第n个丑数
//返回值 :   无
void FindNUglyNumber_Solution1(int n)
{
	int *pArray = new int[n];
	int i = 0;
	int j = 1;
	while(i < n)
	{
		if(IsUglyNumber(j)) //判断一个数是不是丑数
			pArray[i++] = j;
		j++;
	}
	//打印这n个丑数
	for(j = 0; j < n; j++)
		cout<<pArray[j]<<' ';
	cout<<endl;
	//释放空间
	delete [] pArray;
	pArray = NULL;
}

         这个算法太低效了,是否可以改进一下呢?从丑数的构造考虑,其实丑数就是2、3、5的倍数或是它们公倍数。如果能在知道第n个丑数的前提下,知道第n+1个丑数,那么就不需要像穷举法那样判断每个数是不是丑数了。这个思路的关键是确定2、3、5的倍数以及公倍数的顺序关系。比如2^2小于5,所以2^2应该出现在5前面。

        可以定义一个辅助数组用来保存已排好序的丑数,假设当前数组中保存的最大丑数为M,那么下一个丑数肯定是前面某个丑数M2乘以2,或者是前面某个丑数M3乘以3,或者是前面某个丑数M5乘以5。即这三种情况下,乘积最小的那个数。这里的M2、M3、M5不一定是同一个数。

        参考代码:

int MinThreeNum(int x, int y, int z)
{
	int w = (x < y) ? x: y;
	return (w < z)? w : z;
}
void FindNUglyNumber_Solution2(int n)
{
	int *pArray = new int[n];
	int M2, M3, M5;

	pArray[0] = 1;     //第一个丑数
	M2 = M3 = M5 = 0;  //用下标来表示M2、M3、M5

	int nextUgly = 1; //下一个丑数的位置
	while(nextUgly < n)
	{
		int min = MinThreeNum(pArray[M2] * 2, pArray[M3] * 3, pArray[M5] * 5);
		pArray[nextUgly] = min;
		while(pArray[M2] * 2 <= pArray[nextUgly])
			M2++;
		while(pArray[M3] * 3 <= pArray[nextUgly])
			M3++;
		while(pArray[M5] * 5 <= pArray[nextUgly])
			M5++;
		nextUgly++;
	}

	//打印这n个丑数
	for(int i = 0; i < n; i++)
		cout<<pArray[i]<<' ';
	cout<<endl;
	//释放空间
	delete [] pArray;
	pArray = NULL;
}

            本人享有博客文章的版权,转载请标明出处 http://blog.csdn.net/wuzhekai1985

  • 3
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 3
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值