题目一:
从0到2∧32-1,去掉其中某一个数,要求用最少的内存空间找到那个数
思路:
看到这个题目,第一个关注点就是0-2∧32-1。会想到用二进制来表示这些数据,而这2∧32个数,用二进制表示的时候,每一位出现的1和0次数都是一样的。如果少了其中的某一个数,每一位就会少一个0或者一个1。
基于此,我们可以对每一位统计0和1的个数。0少那一位就是说明删除的数该位为0,1少那一位就说明删除的数该位为1.通过这种方式,找到去掉数的二进制表示方式。
这样只需要32位额外的内存空间做辅助,来找到去掉的数。
题目二:斐波那契博弈(Fibonacci Nim)
两个人A和B,先后从一堆棋子里拿棋子,A先手。有以下规则:
- A第一次不能拿走全部
- 后拿的一个人拿走棋子的数目不能超过上一次对方拿走棋子数目的2倍
- 至少拿走1个棋子
- 谁拿走最后一枚棋子,谁就赢
问,在什么情况下,A必败
结论:
当棋子数为Fibonacci数的时候,A必败
即设棋子数为n,n=2.3.5.8.13…时,A必败
证明:
用数学归纳法可证明:
证明过程可参考这个
当n不是Fibonacci数的时候
需要借助“Zeckendorf定理”(齐肯多夫定理):任何正整数可以表示为若干个不连续的Fibonacci数之和。
定理证明这里略过。
分解的时候,要取尽量大的Fibonacci数。
比如分解40:40在34和55之间,于是可以写成40=34+6,然后继续分解6,6在5和8之间,所以可以写成6=5+1,
最后分解成40=34+5+1。
假如n=83 = 55+21+5+2
假如先手取2颗,那么后手无法取5颗或更多,而5是一个Fibonacci数,那么一定是先手取走这5颗石子中的最后一颗,同理,接下去先手取走接下来的后21颗中的最后一颗,再取走后55颗中的最后一颗,即对于以后的每一堆,先手都可以取到这一堆的最后一颗石子,从而获得游戏的胜利。
题目三
给定一个正整数 n,将其拆分为至少两个正整数的和,并使这些整数的乘积最大化。 返回你可以获得的最大乘积。
例如,给定 n = 2,返回1(2 = 1 + 1);给定 n = 10,返回36(10 = 3 + 3 + 4)。
思路:
有穷遍历显然不合适,先尝试列举,看有没有规律发现。
按照题目拆分为至少两个正整数的要求,
2只能拆分为1和1,返回1
3可以拆分为1和2,返回2
4可以拆分为2和2,返回4
5可以拆分为2和3,返回6
6可以拆分为3和3,返回9
7可以拆分为2、2、3,返回12
到这里规律似乎明显了,所有的数最终都可以拆分出若干个2和3,2是最小的偶质数,3是最小的奇质数。
6可以拆分为3、3,也可以拆分成2、2、2,显然3、3的乘积更大。
总结一下规律:
如果正整数n小于3,则返回n-1,如果n>=3,则将n拆分为若干个2和3,且优先拆分出3。
按照优先拆分出3的原则,可以使用一个递归完成:
从数字n大于3,从中拆分出一个3,再将n-3重复这个过程,直到n-3小于等于3。
又因为4可以拆为2、2,4=2*2,所以若n-3为4,也可以停止递归。
代码实现如下:
public static int integerBreak(int n) {
if (n <= 3) {
return n - 1;
}
if (n == 4) {
return 4;
}
// 若n-3<=3,停止递归,又因为4可以拆为2、2,4=2*2,所以若n-3为4,也可以停止递归。
if (n - 3 <= 4) {
return 3 * (n - 3);
}
return 3 * integerBreak(n - 3);
}