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