1、和为S的连续整数序列
描述
小明很喜欢数学,有一天他在做数学作业时,要求计算出9~16的和,他马上就写出了正确答案是100。但是他并不满足于此,他在想究竟有多少种连续的正数序列的和为100(至少包括两个数)。
没多久,他就得到另一组连续正数和为100的序列:18,19,20,21,22。现在把问题交给你,你能不能也很快的找出所有和为S的连续正数序列? Good Luck!
返回值描述:
输出所有和为S的连续正数序列。序列内按照从小至大的顺序,序列间按照开始数字从小到大的顺序。
示例
输入:9
返回值:[[2,3,4],[4,5]]
方法一:滑动窗口
核心思想:
由题意可知,需要根据指定的数,求解连续的正数序列。因此此序列可以看做是等差序列,其中公差为1。由等差序列公式可知:Sn=(p1+pn)*(pn-p1+1)/2。因此本题可以采用滑动窗口来求解正数序列。窗口左侧为p1,窗口右侧为pn,窗口中元素的和为Sn,其中窗口中的序列服从等差序列。初始时,左侧窗口值为1,右侧窗口值为2。求解窗口中的和Sn,并和目标值target进行比较,比较结果有如下三种情况:
1)当 Sn>target 时:左侧窗口p1向右滑动,即元素减少一个,继续求和跟target比较
2)当 Sn=target 时:窗口内的元素即为求解的子序列和,将窗口中的元素存放到二维数组中(每个序列存放在一维数组)。同时窗口pn向右滑动,继续接收新的子序列。
3)当 Sn<target 时:右侧窗口pn向右滑动,即元素增加一个,继续求和跟target比较 循环结束边界条件: 窗口一直缩小到一个元素的时候即退出循环 (题目中序列元素最少为2个,因次p1<pn即为循环条件,等于的时候则退出循环,因为等于的时候窗口缩小到一个元素了)
图解:
核心代码:
vector<vector<int> > FindContinuousSequence(int sum) { vector<vector<int>> result; int p1=1,pn=2,Sn=0,i; //p1窗口左侧值,pn窗口右侧值,Sn记录窗口元素和 while(p1<pn){ Sn=(p1+pn)*(pn-p1+1)/2; //等差数列求窗口值的和 if(Sn>sum){ //窗口值大于目标值,则需要左侧窗口向右滑动缩小窗口 p1++; }else if(Sn == sum){ //窗口值等于目标值,则保存目标窗口序列,同时右侧窗口向右滑动,继续循环其他符合条件的目标窗口序列 vector<int> tmp; for(i=p1;i<=pn;i++) v.push_back(i); result.push_back(tmp); pn++; }else{ //窗口值小于目标值,则需要右侧窗口向右滑动缩扩大窗口 pn++; } } return result; }
时间复杂度O(n)
空间复杂度(1)
方法二:窗口间隔法
核心思想:
由连续子序列的和为target可知,因为序列满足公差为1的等差数列,因此可以根据推到公式得出
Sn=(p1+pn)(pn-p1+1)/2 (1)
假设窗口间隔为dis,则 dis=pn-p1 (2)
由(1)和(2)得出 Sn = (2p1+dis)(dis+1)/2 => p1 = [Sn-dis(dis+1)/2]/(dis+1) (3)
由公式(3)可知,符合的窗口序列必须满足以下条件:
1)Sn > dis(dis+1)/2
2)Sn-dis(dis+1)/2 除以 dis+1必须为正整数
结果处理:由于求出的序列间隔是从小到大,序列间隔越小表示序列中的数值越大,题目要求的实际序列间按照开始数字从小到大的顺序,因此需要将结果序列键进行逆置操作
图解:
核心代码:
vector<vector<int> > FindContinuousSequence(int sum) { vector<vector<int>> result; int dis=1,p1=1,i; //dis记录窗口的间隔,p1记录窗口左侧元素 while(dis*(dis+1)/2 < sum){ //满足第一个条件 if((sum - dis*(dis+1)/2) % (dis+1) == 0){ //满足第二个条件 p1 = (sum - dis*(dis+1)/2) / (dis+1); //求窗口的左侧元素 vector<int> v; for(i=p1;i<=p1+dis;i++) //保存符合条件的序列 v.push_back(i); result.push_back(v); } dis++; } reverse(result.begin(), result.end()); //序列间逆置处理 return result; }
时间复杂度O(n)
空间复杂度O(1)