打印所有和爲s的連續正整數序列(至少含兩個數)

今天看了《劍指offer》裏的這道題,發現如果利用好等差數列的性質,其實可以有更好的解法!

題意

比如,s=15,那麼應該輸出以下三個序列:

7 + 8 = 15
4 + 5 + 6 = 15
1 + 2 + 3 + 4 + 5 = 15

劍指offer的解法

從1開始,枚舉序列的第一個數字,然後順序遞增,直到找到或超過了s。
比如s=9,那麼搜索過程是:
start=1, end=2, 1+2=3 < 9
start=1, end=3, 1+2+3=6 < 9
start=1, end=4, 1+2+3+4=10 > 9,跳過1

start=2, end=3, 2+3=5 < 9,
start=2, end=4, 2+3+4 = 9(get),跳過2

start=3, end=4, 3+4=7 < 9,
start=3, end=5, 3+4+5=12 > 9,跳過3

start=4, end=5, 4+5=9(get,終止了,因爲兩個數字的和爲9是極致了)

這樣的算法的時間複雜度是多少呢?
由等差數列的求和公式sum=(n)(n+1)2 可知,每次枚舉start的時候,最多需要O(sum2) 次,而最多有sum2 個start(序列裏數字的個數從2到根號2*sum,start的個數與序列裏數字的個數一一對應,由此知道最多需要枚舉多少個start),那麼總的複雜度就是O(sum)

線性的,其實已經挺不錯的了。

我的解法

假設序列裏有k個數字,要求的和爲s。

觀察等差數列:
1, 2, 3, 4
其中1 + 4 = 2 + 3 = 5,我們知道,裏面對稱的兩個數字的和必定都相等,那麼就是s / (k/2) = sumOfMiddle,其中sumOfMiddle就是這些兩兩加起來的和,在這裏是5。從sumOfMiddle知道最中間的靠左的數字必定是middleLeft = (sumOfMiddle-1)/2,它是第k/2個數字,由此可以推知序列的第一個數字爲:start = middleLeft - k/2 + 1。

再看奇數個數的序列:
1, 2, 3, 4, 5
其中1 + 5 = 2 + 4 = 3 * 2,類似地,有s / k = middle,其中middle在這裏就是3,就是最中間的這個數字,最中間的數字是序列裏的第(k+1)/2個數字,所以序列的第一個數字爲:start = middle - (k+1)/2 + 1 = middle - (k-1)/2。

有了上面的分析,寫起代碼也很簡單了:

#include <stdio.h>
#include <math.h>


// 是否爲奇數
inline bool isOdd(int n) {
    return (n & 1) != 0;
}

// 是否爲偶數
inline bool isEven(int n) {
    return (n & 1) == 0;
}

// 輸出符合條件的正整數連續序列
void print(int start, int key) {
    int sum = 0;
    for (int i = start; sum < key; ++i) {
        sum += i;
        printf("%d %c ", i, (sum == key) ? '=' : '+');
    }
    printf("%d\n", key);
}


// 計算符合條件的序列的核心,根據等差數列的性質
void cal(int key) {
    int maxNum = sqrt(key << 1);
    for (int i = 2; i <= maxNum; ++i) {
        int half = i >> 1;
        if (isOdd(i) && key % i == 0) {
            int start = key / i - half;
            if (start > 0)
                print(start, key);
        } else if (isEven(i) && key % half == 0) {
            int sumOfMiddle = key / half, start = ((sumOfMiddle-1) >> 1) - half + 1;
            if (isOdd(sumOfMiddle) && start > 0)
                print(start, key);
        }
    }
}


int main() {
    cal(15);
    printf("\n");

    cal(9);
    printf("\n");

    cal(100);
    printf("\n");

    return 0;
}

那麼,不禁問一下,時間複雜度是幾許?

代碼裏只有一重循環,遍歷的是序列裏可能的數字個數,判斷是否滿足條件的時間是O(1),所以整個算法的時間複雜度爲O(sum2) ,是不是比上面的好一點呢?

ps,如果考慮計算機的ALU做除法和取模很慢的話,那麼第一種解法確實是比較實惠的,具體得真機測試才知道孰好孰壞~

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