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

题目

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

解法一:
很吊诡的解法(讲实话,吊诡是什么意思?)。
大概思路是:由于题目要求是连续正数序列,那么可以通过等差数列求和的方法,求任意一个正数序列的和。根据题目要求,和需要等于sum。
假设这个正数序列共有n个数,序列内容为a1, a1 + 1, …, a1 + (n - 1)
则该序列的和为:xxx = sum
由此,可以计算出a1与n的关系 (1)
再根据正数特点,获得n的取值区间 (2)
且a1和n必需是整数 (3)
思路是:n在取值区间内遍历,对于每一个n,求得a1的值。如果a1是整数,说明该长度为n的序列存在,则获得该序列,并加入ans。

    public ArrayList<ArrayList<Integer> > FindContinuousSequence(int sum) {
       // 见过= = 我老是做重复的题真的能有进步么= =
        // 不我错了 我没见过
        ArrayList<ArrayList<Integer>> ans = new ArrayList<ArrayList<Integer>>();
        if(sum <= 1) return ans;
        int maxn = (int)Math.pow(2 * sum + 0.25, 0.5);
        for(int n = maxn; n > 1; n--){
            double a =  (2 * sum - Math.pow(n, 2) + n) / (2 * n);
            if(a == (int) a){
                int b = (int)a;
                int count = 0;
                ArrayList<Integer> list = new ArrayList<Integer>();
                while(count < n){
                    list.add(b++);
                    count++;
                }
                ans.add(list);
            }
        }
        return ans;
    }

讲实话我觉得这方法还挺好的。= =

解法二:
滑动窗口大法好
思路是:维护一个窗口的边界指针,并计算窗口中数字和为add。

  • 如果add<sum,说明还可以向窗口中增加元素,则right++;
  • 如果add > sum,即窗口值大于sum值,说明窗口中元素过多,left++
  • 如果add == sum,则为一个可行序列,加入ans
    移动停止边界:由于这里要求不包含该数字本身,即如果sum=100,返回的值中不包含[100]序列,所以停止情况为:right < sum
    (感觉这里可以改进)
    public ArrayList<ArrayList<Integer>> FindContinuousSequence(int sum) {
        ArrayList<ArrayList<Integer>> ans = new ArrayList<ArrayList<Integer>>();
        if(sum <= 1) return ans;
        int count = 1;
        int left = 1, right = 1;// 滑动窗口边界
        while(right < sum){
            if(count < sum){
                right++;
                count += right;
            }else if(count > sum){
                count -= left;
                left++;
            }else{
                ArrayList<Integer> list = new ArrayList<Integer>();
                for(int i = left; i <= right; i++){
                    list.add(i);
                }
                ans.add(list);
                count -= left;
                left++;
                right++;
                count += right;
            }
        }
        return ans;
    }

解法三:
也是利用数字特点,但简单很多。
我思索了好久= =(我想吃点脑残片治一治
思路是:

链接:https://www.nowcoder.com/questionTerminal/c451a3fd84b64cb19485dad758a55ebe
来源:牛客网

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 < Math.pow(2 * sum, 0.5);

纠结的点在于:n为偶数时,条件是怎么来的?
解释: 比如4,5,6,7,这4个数的和是22,平均值是5.5(小数部分为0.5,说明余数是除数的一半);
22 / 4 = 5…2,即商为5,余数为2。
又有余数 / 4 = 0.5 , 即: 余数 / 0.5 = 4,即 余数 * 2 = 4(4,也就是序列长度)。得证。


    public ArrayList<ArrayList<Integer> > FindContinuousSequence(int sum) {
        ArrayList<ArrayList<Integer>> ans = new ArrayList<>();
        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;
    }
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值