个人感觉,这类题的技巧性很巧,同时需要很好的分析能力,受到各大公司的青睐。下面列举几个,这些问题网上都能找到解答,自己实现了一下,供网友参考。
问题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,求从1到n这n个整数的十进制表示中1出现的次数。例如输入12,从1到12这些整数中包含1 的数字有1,10,11和12,1一共出现了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-5、4-6和7-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; }