和为S的连续正数序列

版权声明:私藏源代码是违反人性的罪恶行为!博客转载无需告知,学无止境。 https://blog.csdn.net/qq_41822235/article/details/82109081

目录

一、  解法一

 二、  解法二

 三、  解法三

四、  解法四(换元法)

五、  解法五

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

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

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

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

        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的那一项,也就是中间项左边的那一项。

        和为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;
        }
    };

 
---------------------  
作者:时代跑得太快  
来源:CSDN  
原文:https://blog.csdn.net/qq_41822235/article/details/82109081  
版权声明:本文为博主原创文章,转载请附上博文链接!

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值