打印所有和为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做除法和取模很慢的话,那么第一种解法确实是比较实惠的,具体得真机测试才知道孰好孰坏~

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值