面試 5:手寫 Java 的 pow() 實現


我們在處理一道編程面試題的時候,通常除了注意代碼規范以外,千萬要記得自己心中模擬一個單元測試。主要通過三方面來處理。

  • 功能性測試
  • 邊界值測試
  • 負面性測試

不管如何,一定要保證自己代碼考慮的全面,而不要簡單地猜想用戶的輸入一定是正確的,只是去實現功能。通常你編寫一個能接受住考驗的代碼,會讓面試官對你刮目相看,你可以不厲害,但已經充分說明了你的靠譜。

今天我們的面試題目是:

面試題:嘗試實現 Java 的 Math.pow(double base,int exponent) 函數算法,計算 base 的 exponent 次方,不得使用庫函數,同時不需要考慮大數問題。

面試題來源於《劍指 Offer》第 11 題,數字的整數次方。

不要介意 Java 真正的方法是 Math.pow(double var1,double var2)。

由於不需要考慮大數問題,不少小伙伴心中暗自竊喜,這題目也太簡單了,給我撞上了,運氣真好,於是直接寫出下面的代碼:

public class Test11 { private static double power(double base, int exponent) { double result = 1.0; for (int i = 0; i < exponent; i++) { result *= base; } return result; } public static void main(String[] args) { System.out.println(power(2, 2)); System.out.println(power(2, 4)); System.out.println(power(3, 1)); System.out.println(power(3, 0)); } } 

寫的快自然是好事,如果正確的話會被面試官認為是思維敏捷。但如果考慮不周的話,恐怕就極容易被面試官認為是不靠譜的人了。在技術能力和靠譜度之間,大多數面試官更青睞於靠譜度。

我們上面確實做到了功能測試,但面試官可能會直接提示我們,假設我們的 exponent 輸入一個負值,能得到正確值么?

跟着自己的代碼走一遍,終於意識到了這個問題,當 exponent 為負數的時候,循環根本就進不去,無論輸入的負數是什么,都會返回 1.0,這顯然是不正確的算法。

我們在數學中學過,給一個數值上負數次方,相當於給這個數值上整數次方再求倒數。

意識到這點,我們修正一下代碼。

public class Test11 { private static double power(double base, int exponent) { // 因為除了 0 以外,任何數值的 0 次方都為 1,所以我們默認為 1.0; // 0 的 0 次方,在數學書是沒有意義的,為了貼切,我們也默認為 1.0 double result = 1.0; // 處理負數次方情況 boolean isNegetive = false; if (exponent < 0) { isNegetive = true; exponent = -exponent; } for (int i = 0; i < exponent; i++) { result *= base; } if (isNegetive) return 1 / result; return result; } public static void main(String[] args) { System.out.println(power(2, 2)); System.out.println(power(2, 4)); System.out.println(power(3, 1)); System.out.println(power(3, -1)); } } 

我們在代碼中增加了一個判斷是否為負數的 isNegetive 變量,當為負數的時候,我們就置為 true,並計算它的絕對值次冪,最后返回結果的時候返回它的倒數。

面試官看到這樣的代碼,可能就有點按捺不住內心的怒火了,不過由於你此前一直面試回答的較好,也打算再給你點機會,面試官提示你,當 base 傳入 0,exponent 傳入負數,會怎樣?

瞬間發現了自己的問題,這不是犯了數學最常見的問題,給 0 求倒數么?

雖然 Java 的 Math.pow() 方法也存在這個問題,但我們這里忽略不計。

於是馬上更新代碼。

public class Test11 { private static double power(double base, int exponent) { // 因為除了 0 以外,任何數值的 0 次方都為 1,所以我們默認為 1.0; // 0 的 0 次方,在數學書是沒有意義的,為了貼切,我們也默認為 1.0 double result = 1.0; // 處理底數為 0 的情況,底數為 0 其他任意次方結果都應該是 0 if (base == 0) return 0.0; // 處理負數次方情況 boolean isNegetive = false; if (exponent < 0) { isNegetive = true; exponent = -exponent; } for (int i = 0; i < exponent; i++) { result *= base; } if (isNegetive) return 1 / result; return result; } public static void main(String[] args) { System.out.println(power(2, 2)); System.out.println(power(2, 4)); System.out.println(power(3, 1)); System.out.println(power(0, -1)); } } 

有了上一次的經驗,這次並不敢直接上交代碼了,而是認真檢查邊界值和各種情況。檢查 1 遍,2 遍,均沒有發現問題,提交代碼。

計算機表示小數均有誤差,這個在 Python 中尤其嚴重,但經數次測試,《劍指 Offer》中講的雙精度誤差問題似乎在 Java 的 == 運算符中並不存在。如有問題,歡迎指正。

上面的代碼基本還算整,健壯性也還不錯,但面試官可能還想問問有沒有更加優秀的算法。

仔細查看,確實似乎是有辦法優化的,比如我們要求 power(2,16) 的值,我們只需要先求出 2 的 8 次方,再平方就可以了;以此類推,我們計算 2 的 8 次方的時候,可以先計算 2 的 4 次方,然后再做平方運算.....妙哉妙哉!

需要注意的是,如果我們的冪數為奇數的話,我們需要在最后再乘一次我們的底數。

我們嘗試修改代碼如下:

public class Test11 { private static double power(double base, int exponent) { // 因為除了 0 以外,任何數值的 0 次方都為 1,所以我們默認為 1.0; // 0 的 0 次方,在數學書是沒有意義的,為了貼切,我們也默認為 1.0 double result = 1.0; // 處理底數為 0 的情況,底數為 0 其他任意次方結果都應該是 0 if (base == 0) return 0.0; // 處理負數次方情況 boolean isNegetive = false; if (exponent < 0) { isNegetive = true; exponent = -exponent; } result = getTheResult(base, exponent); if (isNegetive) return 1 / result; return result; } private static double getTheResult(double base, int exponent) { // 如果指數為0,返回1 if (exponent == 0) { return 1; } // 指數為1,返回底數 if (exponent == 1) { return base; } // 遞歸求一半的值 double result = getTheResult(base, exponent >> 1); // 求最終值,如果是奇數,還要乘一次底數 result *= result; if ((exponent & 0x1) == 1) { result *= base; } return result; } public static void main(String[] args) { System.out.println(power(2, 2)); System.out.println(power(2, 4)); System.out.println(power(3, -1)); System.out.println(power(0.1, 2)); } } 

完美解決。

在提交代碼的時候,還可以主動提示面試官,我們在上面用右移運算符代替了除以 2,用位與運算符代替了求余運算符 % 來判斷是一個奇數還是一個偶數。讓他知道我們對編程的細節真的很重視,這大概也就是細節決定成敗吧。一兩個細節的打動說不定就讓面試官下定決心給我們發放 Offer 了。

位運算的效率比乘除法及求余運算的效率要高的多。

因為移位指令占 2 個機器周期,而乘除法指令占 4 個機器周期。從硬件上看,移位對硬件更容易實現,所以我們更優先用移位。

好了,今天我們的面試精講就到這里,我們明天再見!


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM