题目来源于 LeetCode 上第 1025 号问题:除数博弈。java
对于这种博弈类的题目,若是没有思路的话咱们不妨多举几个例子,尝试着从中找寻规律。算法
N = 1
,爱丽丝没得选择,直接失败,即 鲍勃获胜;N = 2
,爱丽丝有选择,她能够选择 x = 1
,鲍勃面对的就是 N = 2 - 1 = 1
,没法操做,爱丽丝获胜;N = 3
,爱丽丝只能选择 x = 1
,由于选 x = 2
不知足 3 % 2 = 0
,鲍勃面对的就是 N = 3 - 1 = 2
,参考上面 N = 2
的情形,此时鲍勃为 N = 2
的先手,鲍勃获胜;N = 4
,爱丽丝能够选择 x = 1
来使鲍勃遇到 N = 3
的状况,爱丽丝获胜;貌似有个规律:N 为奇数时, 鲍勃获胜;N 为偶数时, 爱丽丝获胜。编程
是这样吗?code
是的。索引
事实上,不管 N 为多大,最终都是在 N = 2 这个临界点结束的。谁最后面对的是 N = 2 的情形,谁就能获胜(这句话不太理解的话,仔细看看 N = 二、N = 3 这两种情形)。three
接下来,咱们得知道一个数学小知识:奇数的因子(约数)只能是奇数,偶数的因子(约数)能够是奇数或偶数。游戏
千万不要忽略 1 也是因子!leetcode
爱丽丝是游戏开始时的先手。get
N - x
替换黑板上的数字 N
,鲍勃面对的就是奇数 N,只能选择 N 的奇数因子 x,奇数 - 奇数 = 偶数
,此时传给爱丽丝的又是偶数。这样轮换下去爱丽丝会遇到 N = 2 的情形,而后获胜;//@五分钟学算法 class Solution { public boolean divisorGame(int N) { return N % 2 == 0; } }
题目来源于 LeetCode 上第 319 号问题:灯泡开关。数学
首先,由于电灯一开始都是关闭的,因此某一盏灯最后若是是点亮的,必然要被按奇数次开关。
咱们假设只有 6 盏灯,并且咱们只看第 6 盏灯。须要进行 6 轮操做对吧,请问对于第 6 盏灯,会被按下几回开关呢?这不可贵出,第 1 轮会被按,第 2 轮,第 3 轮,第 6 轮都会被按。
为何第 一、二、三、6 轮会被按呢?由于 6 = 1×6 = 2×3。通常状况下,因子都是成对出现的,也就是说开关被按的次数通常是偶数次。可是有特殊状况,好比说总共有 16 盏灯,那么第 16 盏灯会被按几回?
16 = 1 × 16 = 2 × 8 = 4 × 4
其中因子 4 重复出现,因此第 16 盏灯会被按 5 次,奇数次。如今你应该理解这个问题为何和平方根有关了吧?
不过,咱们不是要算最后有几盏灯亮着吗,这样直接平方根一下是啥意思呢?稍微思考一下就能理解了。
就假设如今总共有 16 盏灯,咱们求 16 的平方根,等于 4,这就说明最后会有 4 盏灯亮着,它们分别是第 1 × 1 = 1 盏、第 2 × 2=4 盏、第 3 × 3 = 9 盏和第 4 × 4 = 16盏。
咱们不是想求有多少个可开方的数吗,4 是最大的平方根,那么小于 4 的正整数的平方都是在 1~16 内的,是会被按奇数次开关,最终亮着的灯。
就算有的 n 平方根结果是小数,强转成 int 型,也至关于一个最大整数上界,比这个上界小的全部整数,平方后的索引都是最后亮着的灯的索引。因此说咱们直接把平方根转成整数,就是这个问题的答案。
class Solution { public int bulbSwitch(int n) { return (int)Math.sqrt(n); } }
题目来源于 LeetCode 上第 326 号问题:3的幂。
正常的思路是不停地去除以 3,看最后的迭代商是否为 1。这种思路的代码使用到了循环,逼格不够高。
这里取巧的方法 用到了数论的知识:3 的幂次的质因子只有 3。
题目要求输入的是 int 类型,正数范围是 0 - 231,在此范围中容许的最大的 3 的次方数为 319 = 1162261467 ,那么只要看这个数可否被 n 整除便可。
//@五分钟学算法 class Solution { public boolean isPowerOfThree(int n) { return n > 0 && 1162261467 % n == 0; } }
题目来源于 LeetCode 上第 231 号问题:2的幂。
若是一个数是 2 的次方数的话,那么它的二进数必然是最高位为 1,其它都为 0 ,那么若是此时咱们减 1 的话,则最高位会降一位,其他为 0 的位如今都为变为 1,那么咱们把两数相与,就会获得 0。
//@五分钟学算法 class Solution { public: bool isPowerOfTwo(int n) { return n > 0 && ((n & (n - 1)) == 0); } };
题目来源于 LeetCode 上第 172 号问题:阶乘后的零。
题目很好理解,数阶乘后的数字末尾有多少个零。
最简单粗暴的方法就是先乘完再说,而后一个一个数。
事实上,你在使用暴力破解法的过程当中就能发现规律: 这 9 个数字中只有 2(它的倍数) 与 5 (它的倍数)相乘才有 0 出现。
因此,如今问题就变成了这个阶乘数中能配 多少对 2 与 5。
举个复杂点的例子:
10! = 【 2 *( 2 * 2 )* 5 *( 2 * 3 )*( 2 * 2 * 2 )*( 2 * 5)】
在 10!这个阶乘数中能够匹配两对 2 * 5 ,因此10!末尾有 2 个 0。
能够发现,一个数字进行拆分后 2 的个数确定是大于 5 的个数的,因此能匹配多少对取决于 5 的个数。(比如如今男女比例悬殊,最多能有多少对异性情侣取决于女生的多少)。
那么问题又变成了 统计阶乘数里有多少个 5 这个因子。
须要注意的是,像 25,125 这样的不仅含有一个 5 的数字的状况须要考虑进去。
好比 n = 15
。那么在 15!
中 有 3
个 5
(来自其中的5
, 10
, 15
), 因此计算 n/5
就能够 。
可是好比 n=25
,依旧计算 n/5
,能够获得 5
个5
,分别来自其中的5, 10, 15, 20, 25
,可是在 25
中实际上是包含 2
个 5
的,这一点须要注意。
因此除了计算 n/5
, 还要计算 n/5/5 , n/5/5/5 , n/5/5/5/5 , ..., n/5/5/5,,,/5
直到商为0,而后求和便可。
//@五分钟学算法 public class Solution { public int trailingZeroes(int n) { return n == 0 ? 0 : n / 5 + trailingZeroes(n / 5); } }
题目来源于 LeetCode 上第 292 号问题:Nim 游戏。
咱们解决这种问题的思路通常都是反着思考:
若是我能赢,那么最后轮到我取石子的时候必需要剩下 1~3 颗石子,这样我才能一把拿完。
如何营造这样的一个局面呢?显然,若是对手拿的时候只剩 4 颗石子,那么不管他怎么拿,总会剩下 1~3 颗石子,我就能赢。
如何逼迫对手面对 4 颗石子呢?要想办法,让我选择的时候还有 5~7 颗石子,这样的话我就有把握让对方不得不面对 4 颗石子。
如何营造 5~7 颗石子的局面呢?让对手面对 8 颗石子,不管他怎么拿,都会给我剩下 5~7 颗,我就能赢。
这样一直循环下去,咱们发现只要踩到 4 的倍数,就落入了圈套,永远逃不出 4 的倍数,并且必定会输。
public class Solution { bool canWinNim(int n) { // 若是上来就踩到 4 的倍数,那就认输吧 // 不然,能够把对方控制在 4 的倍数,必胜 return n % 4 != 0; } }
题目来源于 LeetCode 上第 877 号问题:石子游戏。
显然,亚历克斯老是赢得 2 堆时的游戏。 经过一些努力,咱们能够获知她老是赢得 4 堆时的游戏。
若是亚历克斯最初得到第一堆,她老是能够拿第三堆。 若是她最初取到第四堆,她老是能够取第二堆。第一 + 第三,第二 + 第四 中的至少一组是更大的,因此她总能获胜。
咱们能够将这个想法扩展到 N 堆的状况下。设第1、第3、第5、第七桩是白色的,第2、第4、第6、第八桩是黑色的。 亚历克斯老是能够拿到全部白色桩或全部黑色桩,其中一种颜色具备的石头数量一定大于另外一种颜色的。
所以,亚历克斯总能赢得比赛。
class Solution { public boolean stoneGame(int[] piles) { return true; } }
❤️ 看完三件事:
若是你以为这篇内容对你挺有启发,我想邀请你帮我三个忙:
点赞,让更多的人也能看到这篇内容(收藏不点赞,都是耍流氓 -_-)关注我和专栏,让咱们成为长期关系关注公众号「五分钟学算法」,第一时间阅读最新的算法文章,公众号后台回复 1024 送你 50 本 算法编程书籍。