剑指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

从起点累加到终点的和由等差数列求和公式易得

\[sum=\frac{(x+x+n-1)*n}{2}=\frac{(2x+n-1)*n}{2} \]

问题转化为是否存在正整数 \(n(n>1)\) ,满足等式

\[sum=\frac{(2x+n-1)*n}{2}=target \]

转化一下变成

\[n^2+(2x-1)n-2*target=0 \]

这是一个关于 \(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;
    }
};
posted @ 2021-01-14 20:58  RiverCold  阅读(60)  评论(0编辑  收藏  举报