今天看了《劍指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是極致了)
這樣的算法的時間複雜度是多少呢?
由等差數列的求和公式
線性的,其實已經挺不錯的了。
我的解法
假設序列裏有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),所以整個算法的時間複雜度爲
ps,如果考慮計算機的ALU做除法和取模很慢的話,那麼第一種解法確實是比較實惠的,具體得真機測試才知道孰好孰壞~