算法思想一:求和公式

解题思路:

设连续正整数序列的左边界 i 和右边界 j ,则此序列的 元素和 tsum 等于 元素平均值 (i+j)/2 乘以 元素数量 (j−i+1) ,即
                                                                        
观察发现,当确定 元素和 tsum 与 左边界 i 时,可通过 解一元二次方程 ,直接计算出右边界 j ,公式推导如下
                                                                        
根据一元二次方程求解:
                                                                        
由于j > i 恒成立,可以直接去掉必为负数的解
因此,通过从小到大遍历左边界 i 来计算 以 i 为起始数字的连续正整数序列 。每轮中,由以上公式计算得到右边界 j ,当 j 满足以下两个条件时记录结果:
    j 为 整数 :符合题目所求「连续正整数序列」;
    i<j :满足题目要求「至少含有两个数」;

代码展示:

Python版本
class Solution:
    def FindContinuousSequence(self, tsum):
        # write code here
        i, j, res = 1, 2, []
        while i < j:
            j = (-1 + (1 + 4 * (2 * tsum + i * i - i)) ** 0.5) / 2
            if i < j and j == int(j):
                res.append(list(range(i, int(j) + 1)))
            i += 1
        return res

复杂度分析:

时间复杂度 O(N): 其中 N =tsum;连续整数序列至少有两个数字,而 i < j恒成立,因此至多循环 tsum/2 次,使用 O(N)
空间复杂度 O(1): 变量 i, j 使用常数大小的额外空间。

算法思想二:双指针

解题思路:

们用两个指针 plow 和 phigh 表示当前枚举到的以 plow 为起点到 phigh  的区间,cur 表示 [plow, phigh] 的区间和,由求和公式可 O(1) 求得为 cur = (phigh + plow) * (phigh - plow + 1) / 2,起始 plow=1,phigh=2。
一共有三种情况:
    如果 cur<sum 则说明指针 phigh 还可以向右拓展使得 cur 增大,此时指针 phigh向右移动,即 phigh+=1
    如果 cur>sum 则说明以 plow为起点不存在一个 phigh 使得 cur =sum,此时要枚举下一个起点,指针 plow 向右移动,即plow+=1
    如果 cur==sum 则说明我们找到了以 plow 为起点得合法解 [plow, phigh] ,我们需要将 [plow, phigh] 的序列放进答案数组,且我们知道以 plow 为起点的合法解最多只有一个,所以需要枚举下一个起点,指针plow 向右移动,即 plow+=1
终止条件即为 plow > phigh 的时候,这种情况的发生指针 phigh 移动到了 (sum/2)+1 的位置,导致 plow < phigh 的时候区间和始终大于 sum

图解:

代码展示:

JAVA版本
import java.util.*;

public class Solution {
    public ArrayList<ArrayList<Integer> > FindContinuousSequence(int sum) {
        ArrayList<ArrayList<Integer>> res = new ArrayList<ArrayList<Integer>>();
        if (sum < 3) {
            return res;
        }
        // 两个起点,相当于动态窗口的两边,根据其窗口内的值的和来确定窗口的位置和大小
        int plow = 1,phigh = 2;
        while(phigh > plow){
            //由于是连续的,差为1的一个序列,那么求和公式是(a0+an)*n/2
            int cur = (phigh + plow) * (phigh - plow + 1) / 2;
            //相等,那么就将窗口范围的所有数添加进结果集
            if(cur == sum){
                ArrayList<Integer> list = new ArrayList<>();
                for(int i=plow;i<=phigh;i++){
                    list.add(i);
                }
                res.add(list);
                plow++;
            //如果当前窗口内的值之和小于sum,那么右边窗口右移一下
            }else if(cur < sum){
                phigh++;
            }else{
            //如果当前窗口内的值之和大于sum,那么左边窗口右移一下
                plow++;
            }
        }
        return res;
    }
}

复杂度分析:

时间复杂度 O(N): 由于两个指针移动均单调不减,且最多移动 (sum/2) 次,即方法一提到的枚举的上界,所以时间复杂度为 O(sum)
空间复杂度 O(1): 除了答案数组只需要常数的空间存放若干变量