這是 leetcode.com 的第二篇。與上一篇一樣,我們討論一道相對簡單的問題,因為學習總講究循序漸進。而且,就算是簡單的問題,追求演算法的極致的話,其中也是有大學問的。
“4”的整數次冪
給定一個32位有符號整數(32 bit signed integer),寫一個函式,檢查這個整數是否是“4”的N次冪,這裡的N是非負整數。
例如:
- 給定 num = 16,返回 true,因為 16 = 42
- 給定 num = 5,返回 flase
附加條件: 你能夠不用迴圈和遞迴嗎?
解題思路
如果忽略“附加條件”,這題還挺簡單的對吧?簡直是信手拈來:
1 2 3 4 5 6 |
function isPowerOfFour(num){ while(!(num % 4)){ num /= 4; } return num === 1; } |
版本1 好像很簡單、很強大的樣子,它的時間複雜度是 log4N。有同學說,還可以做一些微小的改動:
1 2 3 4 5 6 |
function isPowerOfFour(num){ while(!(num % 4)){ num >>>= 2; } return num === 1; } |
上面的程式碼用位移替代除法,在其他語言中更快,鑑於 JS 通常情況下非常坑的位運算操作,不一定速度能變快。
好了,最關鍵的是,不管是 版本1 還是 版本1.1 似乎都不滿足我們前面提到的“附加條件”,即不使用迴圈和遞迴,或者說,我們需要尋找 O(1) 的解法。
按照慣例,大家先思考10秒鐘,然後往下看 ——
不用迴圈和遞迴
其實這道題真心有好多種思路,計算指數之類的對數學系學霸們完全不是問題嘛:
1 2 3 4 5 |
const log4 = Math.log(4); function isPowerOfFour(num){ var n = Math.log(num) / log4; return n === (0|n); } |
嗯,通過對數公式 logm(n) = log(n) / log(m) 求出指數,然後判斷指數是不是一個整數,這樣就可以不用迴圈和遞迴解決問題。而且,還要注意細節,可以將 log4 當做常量抽取出來,這樣不用每次都重複計算,果然是學霸範兒。
不過呢,利用 Math.log 方法也算是某種意義上的犯規吧,有沒有不用數學函式,用原生方法來解決呢?
當然有了!而且還不止一種,大家可以繼續想30秒,要至少想出一種哦 ——
不用內建函式
這個問題的關鍵思路和上一道題類似,先考慮“4”的冪的二進位制表示:
- 40 = 1B
- 41 = 100B
- 42 = 10000B
- 43 = 1000000B
- ……
也就是每個數比上一個數的二進位制後面多兩個零嘛。最重要的是,“4”的冪的二進位制數只有 1 個“1”。如果仔細閱讀過上一篇,你就會知道,判斷一個二進位制數只有 1 個“1”,只需要:
1 |
(num & num - 1) === 0 |
但是,二進位制數只有 1 個“1”只是“4”的冪的必要非充分條件,因為“2”的奇數次冪也只有 1 個“1”。所以,我們還需要附加的判斷:
1 |
(num & num - 1) === 0 && (num & 0xAAAAAAAA) === 0 |
為什麼是 num & 0xAAAAAAAA === 0
? 因為這個確保 num 的二進位制的那個 “1” 出現在“奇數位”上,也就確保了這個數確實是“4”的冪,而不僅僅只是“2”的冪。
最後,我們得到完整的版本:
1 2 3 4 |
function isPowerOfFour(num) { return num > 0 && (num & (num-1)) === 0 && (num & 0xAAAAAAAA) === 0; }; |
上面的程式碼需要加上 num > 0,是因為 0 要排除在外,否則 (0 & -1) === 0 也是 true
其他版本
上面的版本已經符合了我們的需求,時間複雜度是 O(1),不用迴圈和遞迴。
此外,我們還可以有其他的版本,它們嚴格來說有的還是“犯規”,但是我們還是可以學習一下這些思路:
1 2 3 4 |
function isPowerOfFour(num) { num = Math.sqrt(num); return num > 0 && num === (0|num) && (num & (num-1)) === 0; }; |
1 2 3 |
function isPowerOfFour(num) { return /^1(00)*$/g.test(num.toString(2)); }; |
以上就是所有的內容,這道題有非常多種思路,相當有趣,也比較考驗基本功。如果你有自己的思路,可以留言參與討論。
下一期我們討論另外一道題,這道題比這兩道題要難一些,但也更有趣:給定一個正整數 n,將它拆成至少兩個正整數之和,對拆出的正整數求乘積,返回能夠得到的乘積最大的結果。
想一想你的解法是什麼?你能夠儘可能減少演算法的時間複雜度嗎?期待你的答案~~
打賞支援我寫出更多好文章,謝謝!
打賞作者
打賞支援我寫出更多好文章,謝謝!
任選一種支付方式