[剑指offer]和为S的连续正数序列

[剑指offer]和为S的连续正数序列

题目要求

题目描述
小明很喜欢数学,有一天他在做数学作业时,要求计算出9~16的和,他马上就写出了正确答案是100。但是他并不满足于此,他在想究竟有多少种连续的正数序列的和为100(至少包括两个数)。没多久,他就得到另一组连续正数和为100的序列:18,19,20,21,22。现在把问题交给你,你能不能也很快的找出所有和为S的连续正数序列? Good Luck!
输出描述:
输出所有和为S的连续正数序列。序列内按照从小至大的顺序,序列间按照开始数字从小到大的顺序

题目分析

这道题有两种思路。
一种是比较常规的,设置前后的两个标志,对已得到的序列不断进行增加删除,直到超出我们原本设定的范围。
另一种是我在牛客上看到的某大佬的思路,利用了数学的计算方式,将复杂度降到log(n),很巧妙。(所以数学拯救世界啊)
这里将这两种方式都贴出来,并且加以说明。

首先是我的常规思路。

  1. 设置pre指向序列的开始,aft指向序列的结束。那么初始化时,pre=1,aft=2,因为序列是不可以少于两个数的。
  2. 设置Cursum时刻监控当前序列的总和。
  3. 设置一个mid,因为序列中至少有两个数,所以当pre>序列的中间值时,就不需要再继续推进了。mid=(sum+1)/2
  4. 当当前和等于sum时,我们将当前的序列记下来。
  5. 当当前和大于sum时,我们不停地退出序列最前面的那个数。这里不需要考虑pre是否小于aft是因为如果说pre已经大于等于了aft,那么当前和小于等于0,不会再大于sum(这里需要注意的是要在最开始的时候判断sum是否大于等于3,否则的话sum不可能找到相应的正整数序列)要小心,如果退出某个数后,Cursum==sum,需要将当前的序列记下来。
  6. 最后每次我们增加一个序列后端的数进去,这里的巧妙处就在于不要把它放到一个循环里,而是作为每次一定要做的事情,因为sum==Cursum之后我们要继续找别的序列。

解释一下大佬思路,首先是贴一下原解释。

1)由于我们要找的是和为S的连续正数序列,因此这个序列是个公差为1的等差数列,而这个序列的中间值代表了平均值的大小。假设序列长度为n,那么这个序列的中间值可以通过(S / n)得到,知道序列的中间值和长度,也就不难求出这段序列了。
2)满足条件的n分两种情况:
n为奇数时,序列中间的数正好是序列的平均值,所以条件为:(n & 1) == 1 && sum % n == 0;
n为偶数时,序列中间两个数的平均值是序列的平均值,而这个平均值的小数部分为0.5,所以条件为:(sum % n) * 2 == n.
3)由题可知n >= 2,那么n的最大值是多少呢?我们完全可以将n从2到S全部遍历一次,但是大部分遍历是不必要的。为了让n尽可能大,我们让序列从1开始,
根据等差数列的求和公式:S = (1 + n) * n / 2,得到n<根号下(2S)
最后举一个例子,假设输入sum = 100,我们只需遍历n = 13~2的情况(按题意应从大到小遍历),n = 8时,得到序列[9, 10, 11, 12, 13, 14, 15, 16];n = 5时,得到序列[18, 19, 20, 21, 22]。
完整代码:时间复杂度为log(n)

这里主要解释一下(2)中对于条件的判断。

  1. 当n为奇数时,如果sum是n的整数倍,假设是kn,k是正整数,我们可以将n长序列的中间值设为k,这时这个序列的平均值就是k,序列和是sum,满足条件。
  2. 当n为偶数时,如果满足(sum%n)*2==n,那么sum%n==n/2,那么sum=kn+n/2,这时可以设这个序列的平均值为k+0.5,那么序列和是sum,满足条件。

最后说一个比较重要的地方,在这里思路里有一个很重要的对于n的限制,限制在根号下(2S),这不光是为了限制时间复杂度,还是为了限制找到的序列一定都是正数,比如如果不加以限制的话,100%25==0,那么这应该是一个以4为中心,长度25的序列。

代码

//思路1
class Solution {
public:
    vector<vector<int> > FindContinuousSequence(int sum) {
        int pre=1,aft=2,mid=(1+sum)/2;
        int Cursum=3;
        vector<vector<int>>ans;
        if(sum<3)return ans;
        while(pre<mid){
            if(sum==Cursum){
                vector<int>num;
                for(int i=pre;i<=aft;++i){
                    num.push_back(i);
                }
                ans.push_back(num);
            }
            while(Cursum>sum && pre<mid){
                Cursum-=pre;
                ++pre;
                if(sum==Cursum){
                    vector<int>num;
                    for(int i=pre;i<=aft;++i){
                        num.push_back(i);
                    }
                    ans.push_back(num);
                }
            }
            ++aft;
            Cursum+=aft;
        }
        return ans;
    }
};

//大佬思路
import java.util.ArrayList;
public class Solution {
    public ArrayList<ArrayList<Integer> > FindContinuousSequence(int sum) {
        ArrayList<ArrayList<Integer>> ans = new ArrayList<>();
        if(sum<3)return ans;
        for (int n = (int) Math.sqrt(2 * sum); n >= 2; n--) {
            if ((n & 1) == 1 && sum % n == 0 || (sum % n) * 2 == n) {
                ArrayList<Integer> list = new ArrayList<>();
                for (int j = 0, k = (sum / n) - (n - 1) / 2; j < n; j++, k++) {
                    list.add(k);
                }
                ans.add(list);
            }
        }
        return ans;
    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值