《编程之美》笔记
第1章 游戏之乐----游戏中碰到的题目
第2章 数学之魅----数字中的技巧
2.2 不要被阶乘吓倒
问题2:求N!的二进制表示中最低位1的位置。
方法一:一个数字k如果乘以2,则其二进制就会左移一位。那么N!的质因子分解中的2的个数就是N!的二进制中有多少个后缀零。具体实现与N!后缀零一样。
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
1 int lowestOne(int N) 2 { 3 int ans=0; 4 while(N) 5 { 6 N>>=1; 7 ans+=N; 8 } 9 return ans+1;//由于求的是后缀0的个数,所以最后一个1应该是ans+1 10 }
方法二:答案为N!减去其二进制表示中含1的个数。比如 11011-1-1-1-1=(10000-1)+(1000-1)+(10-1)+(1-1)=1111+111+1=1101+110+11+1。需观察规律。
2.3 寻找发帖水王
扩展问题:一个大小为N的数组中有3个特殊的数,这3个数都出现了超过N的1/4次, 求这3个数。
方法:同“寻找出现次数超过一半的数”一样,只不过现在的问题是3个。由于这3个数的出现次数之和超过了N的1/4,那么同样可以用3个桶来做抵消法。具体逻辑为,对于新考虑的数t,若t已经在桶中,则将t继续丢往该桶;若t不在桶中,且有空桶,则将t丢往任一空桶了;若t不在桶中,且无空桶,则随便将任一个数从任一桶中删除。最后留在3桶中的数字就是答案。
2.4 1的数目
问题1:写一个函数f(N),返回1到N之间出现的“1”的个数,比如f(12)=5。
方法一:暴力统计,复杂度太高。
方法二: 类似于数位DP,考虑十进制的第i位分别为0,1和其他的情况就可以解决。当然,数位DP也是可以做。
2.5 寻找最大的K个数
问题:寻找第K大的数(相当于寻找最大的K个数)。
方法一:排序一遍即可解决。
方法二:类似快排,递归不断地进行二分当前的数组。
方法三:找到数组中的最大数big及最小的数small,对答案ans在[big,small]进行二分枚举,每次枚举需要扫一遍整个数组。
方法四:当数组很大时,必须减少遍历数组的次数。若K比较小,可用堆维护当前最大的K个数,pop出一个最小的,加入一个更大的。仅需扫描一遍数组。
方法五:若big-small比较大,可以对其进行分块,判断第K个数具体在哪个块,若还是很大,可以递归继续分快处理。
2.9 斐波那契(Fibonacci)数列
问题:有递推式F(n)=F(n-1)+F(n-2),且F(0)=0,F(1)=1。给定一个自然数N,求F(N)。
方法一:递归求解。缺点是有重复计算,复杂度高。
方法二:递推求解,时间复杂度O(n),空间O(1)。
方法三:求通项公式,用特征根方程求解。缺点是结果为实数,当N较大时有精度问题。
方法四:构造矩阵,用矩阵快速米求解。复杂度O(logn)。
扩展问题:假设A[0]=1,A[1]=2,A[2]=2,对于任意n>=3有A[n]=A[n-1]+A[n-2]+A[n-3]。求A[n]?
方法:构造矩阵法是一般的比较快的解法(当然也可以研究递推式找规律),对于这种递推,右式仅有3项已计算过,仅需要构造一个3*3的矩阵就可以解决。
矩阵应该为:[0, 0 ,1]
[1, 0, 1]
[0, 1, 1]
构造要点:矩阵中的某个格子代表右式中对应项的系数,设A[n]=a*A[n-1]+b*A[n-2]+c*A[n-3],则矩阵为:
[0, 0 ,a]
[b, 0, b]
[0, c, c]
2.15 子数组之和的最大值(二维的最大子段和)
问题:在一个n*m的矩阵中求最大的子矩阵和?
方法一:枚举子矩阵的两个角,再求和。复杂度O(n2*m2*sum的时间复杂度)。为降低求sum的时间复杂度为O(1),预处理部分和。复杂度O(n2*m2)。
方法二:降维思想,假设子矩阵在i~j行之间,则可以按一维的思想求这几列的最大子段和(需要预处理求和)。当然也可以枚举i~j列,复杂度O(n*m*min(n,m))。
扩展问题1:如果矩阵也是首尾相接(行相接),怎么处理?
方法一:假设不经过缺口,枚举i~j行,求列的最大子段和。假设经过缺口,矩阵取反再求一次。
2.21 只考加法的面试题
问题一:判断一个64bit的自然数n是否可能为一组连续的自然数之和(不少于2个),若是,输出所有的可能。
方法:假设有解,设该解中有k个自然数,且最大的自然数为i,则有k*(i+i-k+1)/2=n,即k*(i+i-k+1)=2*n。那么k就是2*n的一个因子,则只需要测试sqrt(2*n)次就可以知道解了。
问题二:在问题一中,哪些自然数是没有解的?
方法:打表知1,2,4,8,16...2j都没有。证明一下,这些数都是2t,它们的因子也只有2j的因子,即除了1之外都是偶数。由k*(2*i-k+1)=2*n得知k和(2*i-k+1) 不能同时为偶数,所以这些数无解。
问题三:在问题一中,某一解中的自然数最多的n是多少?
方法:将16bit内的n都打个表,观察到只有从1+2+...+m=n时才是最多的,即m最大。那么就一直统计前缀和,只要pre_sum最大且小于64bit就是答案了。
第3章 结构之法----字符串及链表的探索
第4章 数学之趣----数学游戏的乐趣
4.7 蚂蚁爬杆
扩展问题4:两人A(速度a),B(速度b)一直在路上相向而行,在A和B之间的距离为s的时候,A放出了一只鸽子C(速度为c),C飞到B后,立即掉头,遇到A后又飞向B,一直往返地飞行,直到AB相遇,问这期间鸽子共飞了多少路程?
方法:A和B会在t=s/(a+b) 单位时间后相遇,而鸽子一直没有停下来,那么鸽子飞了len=t * c的路程(这和鸽子的方向无关)。
扩展问题5:轮船(速度为a)在长江(速度为b)里逆流而上行驶。某个时刻,从穿上落下一个救生圈到水中。一个小时候,船员才发现这一情况,于是掉头去找。问什么时候轮船可找到这个救生圈?
方法:假设船为A,救生圈为B。一个小时后A和B的距离为len=60*b+60*(a-b)=60*a,设经过t分钟A追上B,且走了s路程,那么列出两条式子:(a+b)*t=s,b*t=s-len,其中只有2个变量,联合求解即可得到 t=60。