在開始之前,我覺得應該默唸一遍奧卡姆剃刀原理:“如無必要,勿增實體”。我感覺這一條原則在做算法的時候特別重要:是什麼就是什麼,不應該增加額外的元素。當然,在需要空間換時間(或者空間換時間)的時候,增加輔助元素是有必要的,並不衝突。
爲什麼這麼說呢,且看這兩道題目:
給出一個 32 位的有符號整數,你需要將這個整數中每位上的數字進行反轉。
假設我們的環境只能存儲得下 32 位的有符號整數,則其數值範圍爲 [−2^31, 2^31 − 1]。請根據這個假設,如果反轉後整數溢出,那麼就返回 0。
判斷一個整數是否是迴文數。迴文數是指正序(從左向右)和倒序(從右向左)讀都是一樣的整數;負數不是迴文數。
其實這兩道題從本質上是一樣的,都是對數字的反轉。
但是按照常規思路,第一反應是什麼?我覺得很多人(比如我)都會想到,先把數字變成字符串,然後進行反轉。這個思路看起來很合理,但仔細一想就會覺得不對,因爲明明是數字之間的操作,爲什麼要變成字符串呢?而且在反轉整數的時候,還要對負號進行額外的處理。
/**
* ------result------
* memory: 8 MB (90%)
* speed: 0 ms (100%)
*/
int reverse(int x) {
int res = 0;
while (x != 0) {
// INT32_MIN / -1會溢出,所以需要轉換成long
// 至於爲什麼不用res跟INT32_MAX比較,因爲不能確定res的符號,所以範圍可能會錯誤
if (res != 0 &&
(long)INT32_MAX / res < 10 &&
(long)INT32_MIN / res < 10) {
return 0;
}
res = res * 10 + x % 10;
x /= 10;
}
return res;
}
其實這個做法有點取巧的意思,因爲如果環境不支持long,這個做法就會失效;最好的做法是對最後一位進行判斷。另外,我覺得涉及到倒序對數字的每一位進行操作的問題,秦九韶算法(也就是所謂的霍納算法)都是值得考慮的,因爲這個算法可以以O(n)的時間複雜度對每一位進行操作。
至於迴文數,明明判斷一下反轉後的整數和反轉之前是否相等就夠了,爲什麼要引入字符串呢?但是因爲迴文數的特殊性,這題有特殊的解法:只反轉一半,也就是修改一下反轉結束的判斷條件:
bool isPalindrome(int x) {
// 負數不是迴文數
// 除了0,不存在最後一位爲0的迴文數
if (x < 0 ||
(x % 10 == 0 && x != 0)) {
return false;
}
int res = 0;
// 如果x <= res,說明已經過了一半了
while (x > res) {
res = res * 10 + x % 10;
x /= 10;
}
// 如果是奇數,到這裏還需要額外除以10;因爲過去了一位
return x == res || x == res / 10;
}
我感覺有時候是不是自己提高了算法題的難度……