劍指offer-3-面試題11:數值的整數次方(對錯誤的處理)

題目

實現函數 double Power( double base, int exponent ),求base 的 exponent 次方。不得使用庫函數,同時不需要考慮大數問題。

本題要求實現類似於pow的功能。要求實現特定庫函數(特別是處理數值和字符串的函數)的功能,是一類常見的面試題。

分析

自以爲題目簡單的解法

由於不需要考慮大數問題,這道題 看起來很簡單,可能不少應聘者在看到題目30s後就能寫出如下的代碼:

double Power( double base, it exponent )
{
    double result = 1.0;
    for( int i = 1; i<= exponent; ++i; )
        result *= base;
    return result;
}

不過遺憾的是,寫得快不一定得到面試官的青睞,因爲面試官會問要是輸入的指數( exponent) 小於 1 即是零和負數的時候怎麼辦?上面的代碼完全沒有考慮,只包括了指數是正數的情況。

全面但不夠高效的解法

當指數爲負數的時候,可以先對指數求絕對值,然後算出次方的結果之後再取倒數。既然有求倒數,我們很自然就要想到有沒有可能對0求倒數,如果對0求倒數怎麼辦?當底數(base)是零且指數是負數的時候,如果不做特殊處理,就會出現對0求倒數從而導致程序運行出錯。怎麼告訴函數的調用者出現這種錯誤?前面提到可以採用3種方法:返回值、全局代碼和異常。面試的時候可以向面試官闡述每種方法的優缺點,然後一起討論決定選用哪種方式。

最後需要指出的是,由於0的0次方在數學上是沒有意義的,因此無論是輸出0還是1都是可以接受的,但這都需要和面試官說清楚,表明我們已經考慮到這個邊界值了。

bool g_InvalidInput = false;
double Power( double base, int exponent )
{
    g_InvalidInput = false;
    if( equal( base, 0.0 )&& exponent < 0 )
    {
        g_InvalidInput = true;
        return 0.0;
    }

    unsigned int absExponent = ( unsigned int ) ( exponent );
    if( exponent < 0 ) 
        absExponent = ( unsigned int )( -exponent );
    double result = PowerWithUnsignedExponent( base, absExponent );
    if( exponent < 0 )
        result = 1.0 / result;
    return result;
} 
double PowerWithUnsignedExponent( double base, unsigned int exponent )
{
    double result = 1.0;
    for( int i = 1; i<= exponent; ++i )
        result *= base;
    return result;
}
bool equal( double num1, double num2 )
{
    if( (num1-num2 > -0.0000001) && (num1-num2 < 0.0000001 ))
        return true;
    else 
        return false;
}

上述代碼中我們採用的是全局變量來標識是否出錯。如果出錯了,則返回的值是0.但爲了區分是出錯的時候返回的0,還是底數爲0的時候正常運行返回的0,我們還定義了一個全局變量 g_InvalidInput。當出錯時,這個變量被設爲true,否則爲false。這樣做的好處是,可以把返回值直接傳遞給其他變量,比如寫 double result = Power( 2,3 )。也可以把函數的返回值直接傳遞給其他需要double 型參數的函數。但缺點是這個函數的調用者有可能會忘記檢查g_InvalidInput以判斷是否出錯,留下了安全隱患。由於有優點也有缺點,因此在寫代碼之前要和面試官討論採用哪種出錯處理方式最合適。

一個細節值得我們注意:在判斷底數base是不是等於0時,不能直接寫 base == 0,這是因爲在計算機內表示小數時(包括float 和 double型小數)都有誤差。判斷兩個小數是否相等,只能判斷它們之差的絕對值是不是在一個很小的範圍內。如果兩個數相差很小,就可以認爲它們相等。這就是我們定義函數equal的原因。

看代碼可以看出 PowerWithUnsignedExponent 還有更快的辦法。

全面又高效的解法

如果輸入的指數 exponent 爲 32,我們在函數PowerWithUnsignedExponent的循環中需要做31次乘法。但是可以換一種思路考慮:我們的目標是求出一個數字的32次方,如果我們已經知道了它的16次方,那麼只要在16次方的基礎上再平方一次就可以了。而16次方是8次方的平方。這樣以此類推,我們求32次方只需要做5次乘法:先求平方,在平方的基礎上求4次方,在4次方的基礎上求8次方,在8次方的基礎上求16次方,最後在16次方的基礎上求32次方。
這裏寫圖片描述

double PowerWithUnsignedExponent( double base, unsigned int exponent )
{
    if( exponent == 0 )
        return 1;
    if( exponent == 1 )
        return base;
    double result = PowerWithUnsignedExponent( base, exponent >> 1 );
    result *= result;
    if( exponent & 0x1 == 1 )
        result *= base;
    return result;
}

最後提醒一個細節:用右移運算符代替了除以2,用位與運算符代替了求餘運算符(%)來判斷一個數是奇數還是偶數。位運算的效率比乘除法及求餘運算的效率要高很多。既然要優化代碼,我們就把優化做到極致。

在面試的時候,我們可以主動提醒面試官注意代碼中的兩處細節(判斷base是否等於0和用位運算替代乘除法及求餘運算),讓他知道我們對編程的細節很重視。細節很重要,因爲,細節決定成敗。

測試用例&代碼

把底數和指數分別設爲正數、負數和零

本題考點

(1)思維的全面性。這個問題本身不難,但是能順利通過的應聘者不是很多。有很多人會忽視底數爲0而指數爲負數時的錯誤處理

(2)對效率要求比較高的面試官還會考查應聘者快速做乘方的能力。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章