第一道:除數博弈
題目來源於 LeetCode 上第 1025 號問題:除數博弈。
題目解析
對於這種博弈類的題目,如果沒有思路的話我們不妨多舉幾個例子,嘗試着從中找尋規律。
- 假設
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 爲偶數時, 愛麗絲獲勝。
是這樣嗎?
是的。
事實上,無論 N 爲多大,最終都是在 N = 2 這個臨界點結束的。誰最後面對的是 N = 2 的情形,誰就能獲勝(這句話不太理解的話,仔細看看 N = 2、N = 3 這兩種情形)。
接下來,我們得知道一個數學小知識:奇數的因子(約數)只能是奇數,偶數的因子(約數)可以是奇數或偶數。
千萬不要忽略 1 也是因子!
愛麗絲是遊戲開始時的先手。
- 當她面對的 N 爲偶數時,她 一定可以 選到一個 N 的奇數因子 x(比如 1 ),將 N - x 這個奇數傳給鮑勃;用
N - x
替換黑板上的數字N
,鮑勃面對的就是奇數 N,只能選擇 N 的奇數因子 x,奇數 - 奇數 = 偶數
,此時傳給愛麗絲的又是偶數。這樣輪換下去愛麗絲會遇到 N = 2 的情形,然後獲勝; - 當愛麗絲遇到的 N 是奇數時,只能傳給鮑勃偶數或無法操作 (N = 1) ,無法獲勝。
代碼實現
//@五分鐘學算法
class Solution {
public boolean divisorGame(int N) {
return N % 2 == 0;
}
}
第二道:燈泡開關
題目來源於 LeetCode 上第 319 號問題:燈泡開關。
題目分析
首先,因爲電燈一開始都是關閉的,所以某一盞燈最後如果是點亮的,必然要被按奇數次開關。
我們假設只有 6 盞燈,而且我們只看第 6 盞燈。需要進行 6 輪操作對吧,請問對於第 6 盞燈,會被按下幾次開關呢?這不難得出,第 1 輪會被按,第 2 輪,第 3 輪,第 6 輪都會被按。
爲什麼第 1、2、3、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);
}
}
第三道:3的冪
題目來源於 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;
}
}
第四道:2的冪
題目來源於 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);
}
}
第六道:Nim 遊戲
題目來源於 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 堆的情況下。設第一、第三、第五、第七樁是白色的,第二、第四、第六、第八樁是黑色的。 亞歷克斯總是可以拿到所有白色樁或所有黑色樁,其中一種顏色具有的石頭數量必定大於另一種顏色的。
因此,亞歷克斯總能贏得比賽。
代碼實現
class Solution {
public boolean stoneGame(int[] piles) {
return true;
}
}