題目來源於 LeetCode 上第 1025 號問題:除數博弈。算法
對於這種博弈類的題目,若是沒有思路的話咱們不妨多舉幾個例子,嘗試着從中找尋規律。spa
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
是這樣嗎?索引
是的。three
事實上,不管 N 爲多大,最終都是在 N = 2 這個臨界點結束的。誰最後面對的是 N = 2 的情形,誰就能獲勝(這句話不太理解的話,仔細看看 N = 二、N = 3 這兩種情形)。遊戲
接下來,咱們得知道一個數學小知識:奇數的因子(約數)只能是奇數,偶數的因子(約數)能夠是奇數或偶數。leetcode
千萬不要忽略 1 也是因子!get
愛麗絲是遊戲開始時的先手。數學
N - x
替換黑板上的數字 N
,鮑勃面對的就是奇數 N,只能選擇 N 的奇數因子 x,奇數 - 奇數 = 偶數
,此時傳給愛麗絲的又是偶數。這樣輪換下去愛麗絲會遇到 N = 2 的情形,而後獲勝; //@五分鐘學算法
class Solution {
public boolean divisorGame(int N) {
return N % 2 == 0;
}
}複製代碼
題目來源於 LeetCode 上第 319 號問題:燈泡開關。it
首先,由於電燈一開始都是關閉的,因此某一盞燈最後若是是點亮的,必然要被按奇數次開關。
咱們假設只有 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;
}
}複製代碼