剑指offer41_和为S的连续正数序列_题解
和为S的连续正数序列
题目描述
小明很喜欢数学,有一天他在做数学作业时,要求计算出9~16的和,他马上就写出了正确答案是100。但是他并不满足于此,他在想究竟有多少种连续的正数序列的和为100(至少包括两个数)。没多久,他就得到另一组连续正数和为100的序列:18,19,20,21,22。现在把问题交给你,你能不能也很快的找出所有和为S的连续正数序列? Good Luck!
返回值描述
输出所有和为S的连续正数序列。序列内按照从小至大的顺序,序列间按照开始数字从小到大的顺序
示例1
输入
9
返回值
[[2,3,4],[4,5]]
分析
方案一:枚举+数学优化
假设起点为 x
,连续正数序列的长度为n
,则终点为x+n-1
从起点累加到终点的和由等差数列求和公式易得
问题转化为是否存在正整数 \(n(n>1)\) ,满足等式
转化一下变成
这是一个关于 \(n\) 的一元二次方程,其中 \(a=1,b=(2x-1),c=-2*target\) ,套用求根公式即解得 \(n\),判断是否整数解需要满足两个条件:
- 判别式开根需要为整数
- 最后的求根公式的分子需要为偶数,因为分母为 2
代码
/**
1.时间复杂度:O(target)
由于枚举以后只需要 O(1) 的时间判断,所以时间复杂度为枚举起点的复杂度O(target)
2.空间复杂度:O(1)
除了答案数组只需要常数的空间存放若干变量。
**/
class Solution
{
public:
vector<vector<int>> findContinuousSequence(int target)
{
vector<vector<int>> ans;
vector<int> tmp;
int limit = target / 2; //枚举上界为target/2
for (int i = 1; i <= limit; ++i)
{
// 其中用了1LL。LL其实代表long long, * 1LL是为了在计算时,把int类型的变量转化为long long,然后再赋值给long long类型的变量。
long long delta = 1ll * pow(2 * i - 1, 2) + 8 * target;
//判断判别式delta是否大于0
if (delta >= 0)
{
int sqr = (int)sqrt(delta + 0.5);
//判断判别式的平方根是否为整数
if (1ll * sqr * sqr == delta)
{
//求根公式的分子需要为偶数,因为分母为2
if (((1 - 2 * i) + sqr) % 2 == 0)
{
tmp.clear();//别忘了清空临时数组
int n = ((1 - 2 * i) + sqr) / 2;
for (int k = i; k < i + n; ++k)
tmp.emplace_back(k);
ans.emplace_back(tmp);
}
}
}
}
return ans;
}
};
方案二:滑动窗口
我们设滑动窗口的左边界为 \(i\),右边界为 \(j\),则滑动窗口框起来的是一个左闭右开区间 \([i, j)\)。
注意,为了编程的方便,滑动窗口一般表示成一个左闭右开区间。在一开始,\(i=1, j=1\) 滑动窗口位于序列的最左侧,窗口大小为零。
当窗口的和小于 \(target\) 的时候,窗口的和需要增加,所以要扩大窗口,窗口的右边界向右移动
当窗口的和大于 \(target\) 的时候,窗口的和需要减少,所以要缩小窗口,窗口的左边界向右移动
当窗口的和恰好等于 \(target\) 的时候,我们需要记录此时的结果。设此时的窗口为 \([i, j)\),那么我们已经找到了一个 \(i\) 开头的序列,也是
唯一一个 \(i\) 开头的序列,接下来需要找 \(i+1\) 开头的序列,所以窗口的左边界要向右移动
代码
/**
1.时间复杂度:O(n)
2.空间复杂度:O(1)
**/
class Solution
{
public:
vector<vector<int>> FindContinuousSequence(int target)
{
int i = 1; //滑动窗口的左边界
int j = 1; //滑动窗口的右边界
int sum = 0; //滑动窗口中数字的和
vector<vector<int>> res;
while (i <= target / 2)
{
if (sum < target)
{
//右边界向右移动
sum += j;
j++;
}
else if (sum > target)
{
//左边界向右移动
sum -= i;
i++;
}
else
{
//记录结果
vector<int> arr;
for (int k = i; k < j; k++)
arr.emplace_back(k);
res.emplace_back(arr);
//左边界向右移动
sum -= i;
i++;
}
}
return res;
}
};