面試題之發散思維能力:如何用非常規方法求1+2+···+n


今天在《劍指offer》里看到了下面這樣一個簡單且有趣的題,考察程序員的發散思維能力,前提是你對C++相關知識點熟悉,否則是想不出來方案的,分享給大家。
 
題目:求1+2+···+n,要求不能使用乘除法、for、while、if、else、switch、case等關鍵字及條件判斷語句(A?B:C)。
 
點評:這個問題本身沒有太多的實際意義,因為在軟件開發中不可能有這么苛刻的限制。但不少面試官認為這是一道不錯的能夠考查應聘者發散思維能力的題目,而發散思維能夠反映出應聘者知識面的寬度,以及對編程相關技術理解的深度。
 
分析:通常求1+2+ ··· +n有三種常規思路(1)循環;(2)遞歸(需要用if語句或者條件判斷語句來判斷是繼續遞歸下去還是終止遞歸);(3)使用公式n*(n+1)/2。因為題目限制,這三種常規方案都不可行。那么如何用非常規方案求解該問題呢?下面提供兩種解題思路,對應四種解決方案,本質上還是采用循環或遞歸的思路,只是寫法改變了。
 
解題思路一:循環角度
基本思想:從循環角度思考。循環只是讓相同的代碼重復執行n遍而已,我們完全可以不用for和while來達到這個目的。比如下面采用的構造函數法。

方案一:利用構造函數求解

具體方案:我們先定義一個類型,接着創建n個該類型的實例,那么這個類型的構造函數將確定會被調用n次。我們可以將與累加相關的代碼放到構造函數里。
實現代碼:
class Temp
{
public:
    Temp() { ++N; Sum += N; }
    static void init() { N = 0; Sum = 0; }
    static unsigned int getSum() { return Sum; }

private:
    static unsigned int N;
    static unsigned int Sum;
};

unsigned int Temp::N = 0;
unsigned int Temp::Sum = 0;

unsigned int sum_solution1(unsigned int n)
{
    Temp::init();

    Temp *temp = new Temp[n];
    delete []temp;
    temp = NULL;

    return Temp::getSum();
}
 
 
解題思路二:遞歸角度
基本思想:從遞歸角度思考。根據題目限制我們不能在一個函數中判斷遞歸終止的條件,那么我們不妨定義兩個函數:一個函數充當原遞歸函數中遞歸的角色,另一個函數處理遞歸終止的情況,我們需要做的就是在兩個函數里二選一。從二選一我們很自然的想到布爾變量,比如值為true(1)的時候調用第一個函數,值為false(0)的時候調用第二個函數。那如何將數值變量n轉換成布爾變量呢?解決方法就是對n連續做兩次取反運算,即!!n,這樣非零的n就會轉換為true,0就轉換為false。
基於該思想有下面三種解決方案。
 

方案二:利用虛函數求解

具體方案用虛函數來實現函數的選擇。比如父類虛函數處理遞歸終止,子類虛函數實現遞歸累加,那么當n不為0時,調用子類函數,n為0時調用父類函數即可完成計算。
實現代碼
class Base;
Base* array[2];

class Base
{
public:
    virtual unsigned int sum(unsigned int n)
    {
        return 0;
    }
};

class Derived : public Base
{
public:
    virtual unsigned int sum(unsigned int n)
    {
        return array[!!n]->sum(n - 1) + n;
    }
};

unsigned int sum_solution2(unsigned int n)
{
    Base b;
    Derived d;
    array[0] = &b;
    array[1] = &d;

    return array[1]->sum(n);
}
 

方案三:利用函數指針求解

具體方案在純C語言的編程環境中,我們不能使用虛函數,此時可以用函數指針來模擬,這樣代碼可能還更加直觀一些。
實現代碼
typedef unsigned int (*fun)(unsigned int);

unsigned int sum_terminator(unsigned int n)
{
    return 0;
}

unsigned int sum_solution3(unsigned int n)
{
    fun f[2] = {sum_terminator, sum_solution3};
    
    return f[!!n](n - 1) + n;
}
 

方案四:利用模板類型求解

具體方案:最后我們還可以讓編譯器來幫助完成類似於遞歸的計算。例如利用模板類型,以n作為模板參數。
實現代碼
template <unsigned int n> struct sum
{
    enum value { N = sum<n - 1>::N + n };
};

template <> struct sum<1>
{
    enum value { N = 1 };
};
 
說明sum<100>::N就是1+2+···+100的結果。該方法有明顯的缺陷,由於該計算過程是在編譯過程中完成的,因此要求輸入n必須是在編譯期間就能確定的常量,也就是我們不能動態輸入n,這是最大的缺點,而且編譯器對遞歸編譯代碼的遞歸深度是有限制的,也就是要求n不能太大。
 
綜合測試代碼:
#include <iostream>
using namespace std;

unsigned int sum_solution1(unsigned int n);
unsigned int sum_solution2(unsigned int n);
unsigned int sum_solution3(unsigned int n);
unsigned int sum_solution4(unsigned int n);

unsigned int main()
{
    unsigned int n;
    scanf("%d", &n);
    printf("sum_solution1(%d): %d\n", n, sum_solution1(n));
    printf("sum_solution2(%d): %d\n", n, sum_solution2(n));
    printf("sum_solution3(%d): %d\n", n, sum_solution3(n));
    printf("sum_solution4(%d): %d\n", 100, sum_solution4(n));

    return 0;
}

/************** solution 1 ***************/
class Temp
{
public:
    Temp() { ++N; Sum += N; }
    static void init() { N = 0; Sum = 0; }
    static unsigned int getSum() { return Sum; }

private:
    static unsigned int N;
    static unsigned int Sum;
};

unsigned int Temp::N = 0;
unsigned int Temp::Sum = 0;

unsigned int sum_solution1(unsigned int n)
{
    Temp::init();

//    Temp temp[] = new Temp[n];
    Temp *temp = new Temp[n];
    delete []temp;
    temp = NULL;

    return Temp::getSum();
}

/************** solution 2 ***************/
class Base;
Base* array[2];

class Base
{
public:
    virtual unsigned int sum(unsigned int n)
    {
        return 0;
    }
};

class Derived : public Base
{
public:
    virtual unsigned int sum(unsigned int n)
    {
        return array[!!n]->sum(n - 1) + n;
    }
};

unsigned int sum_solution2(unsigned int n)
{
    Base b;
    Derived d;
    array[0] = &b;
    array[1] = &d;

    return array[1]->sum(n);
}

/************** solution 3 ***************/
typedef unsigned int (*fun)(unsigned int);

unsigned int sum_terminator(unsigned int n)
{
    return 0;
}

unsigned int sum_solution3(unsigned int n)
{
    fun f[2] = {sum_terminator, sum_solution3};
    
    return f[!!n](n - 1) + n;
}

/************** solution 4 ***************/
template <unsigned int n> struct sum
{
    enum value { N = sum<n - 1>::N + n };
};

template <> struct sum<1>
{
    enum value { N = 1 };
};

unsigned int sum_solution4(unsigned int n)
{
    return sum<100>::N;  // 必須指定常數
}
View Code

運行結果:

 
大家還能想出其它的解決方案嗎?歡迎補充~~~
 
------------------------------------分割線-----------------------------------------
2014.2.12更新:
感謝博客園網友張海拔的補充,提供了下面這個非常簡單的解決方案:
#include <stdio.h>
 
int main(void)
{
    int n;
    scanf("%d", &n);
    char a[n][n];
    printf("sum = %d\n", ((int)sizeof a + n)>>1);
    return 0;
}

在Linux下用gcc測試的確可行,不過需要C99可變長參數的支持,因此在微軟編輯器下是編譯不成功的。本文只從三種常規方案的循環和遞歸兩種方案提供解題思路,這里從公式n*(n+1)/2出發,提供了第三種解題思路,不失為一種好的解決方案。


免責聲明!

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



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