和为S的连续正数序列

版权声明:本文为博主原创文章,遵循 CC 4.0 by-sa 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/qq_41822235/article/details/82109081

目录

一、  解法一

 二、  解法二 

 三、  解法三

四、  解法四(换元法) 

五、  解法五


要求计算出9~16的和,写出正确答案是100。究竟有多少种连续的正数序列的和为100(至少包括两个数)。另一组连续正数和为100的序列:18,19,20,21,22。现有问题如下,能不能也很快的找出所有和为S的连续正数序列?

输出所有和为S的正数序列。序列内按照从小至大的顺序,序列间按照开始数字从小到大的顺序。


一、  解法一

数学知识偏多一点,但也不是难到无法求解,稍微麻烦一点就是了。首先我们需要回答三个问题。

  1. n = 2k + 1时,n项连续正数序列的和为S的条件:     n & 1 && S / n == 0 解读 逻辑与的左边要求n为奇数,右边要求整个序列的平均数恰好为中间数。

  2. n = 2k时,n项连续正数序列的和为S的条件:  S % n * 2 == n  解读  S % n 的结果是中间两项左边的那项,乘2刚好是项数。举例,现有S = 39,6个连续正数序列和式能不能为S呢?套用公式,39 % 6 * 2 =6 == 6,我们也知道,这次的序列是 4、5、6、7、8、9,取余的结果为3对应着值为6的那一项,也就是中间项左边的那一项。

  3. 和为S,项数为n,如何写出这个序列?  S / n - (n-1) / 2  解读  执行的除法是地板除法(floor),不管最终结果有无小数都直接舍去。仍使用上述例子,39 / 6 = 6,6恰好是中间项左边的那一项,6 - (6-1)/ 2 = 4,恰好是序列最左端。序列写出来就没问题。

class Solution {
public:
    vector<vector<int> > FindContinuousSequence(int sum) {

        vector<vector<int> > allRes;
        for (int n = sqrt(2 * sum); n >= 2; --n) {
            if (((n & 1) == 1 && sum % n == 0) || (sum % n * 2 == n)){
                vector<int> res;
                //j用于计数,k用于遍历求值
                for(int j = 0,k = sum / n - (n - 1) / 2; j < n; j++, k++)
                    res.push_back(k);
                allRes.push_back(res);
            }  
        }
        return allRes;
    }
};

还有一点需要注意,S = (a0 + a0 +n-1)*  n / 2,等差序列公式自然是不必说的。对其进行放缩,就有S > n平方 / 2;即n < 根号2S这一点在解法4中也有涉及)。这样做的话可以减少遍历次数。 在for循环中就有体现。

for (int n = sqrt(2 * sum); n >= 2; --n)

时间复杂度为O(根号sum )


 二、  解法二 

暴力求解,类似于TCP的滑动窗口协议。

class Solution {
public:
    vector<vector<int> > FindContinuousSequence(int sum) {
        //至少包含2个数
        vector<vector<int> > allRes;
        int low = 1;
        int high = 2;
        while(high > low){    //终止条件,因为题干要求至少2个数
            //连续且差为1的序列,求和公式可知
            int cur = (low + high) * (high - low + 1) / 2;
            if(cur < sum)    //当前窗口内的值之和小于sum,右框右移一下
                high++;
            else if(cur == sum){   //相等,就将窗口内所有数添加进结果集
                vector<int> res;
                for(int i = low; i <= high; ++i)
                    res.push_back(i);
                allRes.push_back(res);
                low++;
            }    
            else    //如果当前窗口内的值之和大于sum,左窗框左移一下
                low++;   
        }
        return allRes;
    }
};

 三、  解法三

同样是受到了TCP滑动窗口协议的启发。还有迭代的想法在里面,也可以是说状态转移方程

用begin和end分别表示序列的左值右值,首先将begin初始化为1,end初始化为2;

  • 若[begin, end]之和 > S,从序列中去掉第一个值(增大begin);
  • 若和 < S,增大end,和中加入新的end;
  • 等于S,将[begin, end] 纳入到结果集中;
class Solution {
public:
    vector<vector<int> > FindContinuousSequence(int sum) {
        vector<vector<int> > allRes;
        if(sum < 3) return allRes;    //至少都要有2项
        int mid = (sum + 1) >> 1;    //向上取整,故而begin只能逼近而不能取到
        int begin = 1;
        int end = 2;
        int cur = begin + end;
        while(begin < mid)
        {
            while(cur > sum){    //这个循环的隐患在于循环结束时,beign == end而且和等于sum
                cur -= begin;
                begin++;
            }
            if(cur == sum && begin < end)        //防止出现某个数组只有1项的情况
                InsertRes(begin, end, allRes);
            
            end++;
            cur += end;
        }
        return allRes;
    }
    void InsertRes(int begin, int end, vector<vector<int>> &allRes){
        vector<int> temp;
        for(int i = begin; i <= end; ++i){
            temp.push_back(i);
        }
        allRes.push_back(temp);
    }
};


四、  解法四(换元法)  

根据所学知识,有:

(a + b)(b - a + 1) = 2 * sum;此为等差数列公式。

i = b - a + 1(项数), j = a + b(首末项之和);现讨论取值范围。i >= 2(序列中至少有2项), j >= 3(序列之和至少为3);隐藏的关系是: j > i同时还有 i * j = 2 * sum,进行放缩之后就有 i * i < 2 * sum,即 i < 根号(2 * sum)。对i进行遍历,找出i,j∈正整数j - i + 1为偶的取值。

class Solution {
public:
    vector<vector<int> > FindContinuousSequence(int sum) {
        vector<vector<int> > allRes;
        if(sum < 3)
            return allRes;
        
        int twofoldSum = 2 * sum;
        int i,j;
        for(i = sqrt(twofoldSum); i >= 2; --i)    //枚举出合适的项数
        {
            if(twofoldSum % i == 0)    //首末项之和为整数
            {
                j = twofoldSum / i;    //求出首末项之和
                int temp = j - i + 1;    //解方程得到2倍的左值等于j - i + 1;要求左边界为整数
                if (!(temp & 1))        //要求temp是偶数
                {
                    int begin = temp >> 1;
                    int end = j - begin;    //j的实际意义是首末项之和
                    vector<int> res;
                    InsertRes(begin, end, res);
                    allRes.push_back(res);
                }
            }
        }
        return allRes;
    }
    
    void InsertRes(int begin, int end, vector<int> &res)
    {
        for(int i = begin; i <= end; ++i)
            res.push_back(i);
    }
};


五、  解法五 

突破口仍然是先求出项数,然后求出序列第一项;与解法一相差不大。但具体细节上还有较大差异。

class Solution {
public:
    vector<vector<int> > FindContinuousSequence(int sum) {
        vector<vector<int> > allRes;
        if(sum < 3)
            return allRes;
        for(int k = sqrt(2 * sum); k >= 2; --k)    //k为项数
        {
            double cheat = (2.0 * sum / k - k + 1) / 2.0;    //为了验证首项是否为整数
            int begin = cheat;
            if(begin == cheat)        //首项确实为整数
            {
                 vector<int> res;
                 for(int i = begin; i < begin + k; ++i)
                     res.push_back(i);
                 allRes.push_back(res);
            }
        }
        return allRes;
    }
};

展开阅读全文

没有更多推荐了,返回首页