和为S的连续正数序列

题目描述

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

输出描述:

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

解法一(感觉这种方法在面试时不会通过的):

通常看到这种题目,第一想法肯定是逐个遍历。

两个指针,ij = i + 1

  • 当从ij的和小于sum时,j 右移
  • 当从ij的和大于sum时,i 右移,然后 j = i + 1
  • 当等于时,把 i j 的值放入 vector
clss Solution {
public:
    vector<vector<int> > FindContinuousSequence(int sum) {
        vector<vector<int>> vec;
        int length = sum / 2;
        for (int i = 1; i <=length; i++){
            int m = 0;
            for (int j = i; j <= length + 1; j++) {
                m += j;
                if (m == sum) {
                    vec.push_back(generate(i, j));
                    break;
                }
                else if (m > sum)
                    break;
            }
        }
        return vec;
    }
private:
    vector<int> generate(int a, int b) {
        vector<int> vec;
        if (a < b) {
            int i;
            for (i = a; i != b; i++)
                vec.push_back(i);
            vec.push_back(b);
        }
        return vec;
    }
};

解法二:

双指针解法,类似于计算机网络中的滑动窗口

low,high

  •  如果 low 到 high 的和小于 sum,则 high 右移
  •  如果 low 到 high 的和大于 sum,则 low 右移
  •  如果 low 到 high 的和等于 sum,那么这个区间就是其中一个解,然后 low 右移

这种方法相较于第一种方法,时间复杂度会小一点

class Solution {
public:
    vector<vector<int> > FindContinuousSequence(int sum) {
        vector<vector<int> > allRes;
        int phigh = 2, plow = 1;
        while (phigh > plow) {
            int cur = (phigh + plow) * (phigh - plow + 1) / 2;
            if (cur < sum)
                phigh++;
            if (cur == sum) {
                vector<int> res;
                for (int i = plow; i <= phigh; i++)
                    res.push_back(i);
                allRes.push_back(res);
                plow++;
            }
            if (cur > sum)
                plow++;
        }
        return allRes;
    }
};

解法三(最优解):

  •  因为是和为S的连续正数序列,因此这个序列是个公差为1的等差数列,而这个序列的中间值就代表了平均值的大小。假设序列长度为n,则中间值为(S/n),知道了中间值和长度,那么就可以得到这个序列
  •  对于n为奇数的情况,中间值即为(S/n),即满足条件如下:

(n & 1) == 1(因为==的运算符优先级高于&,所以用括号括起来)

S % n == 0;  

  • 对于n为偶数的情况,中间值为(S/n),在此情况下会产生小数位,即0.5,所以应该满足条件如下:

(S % n) * 2 == n

  1. 稍微解释一下,因为小数位是0.5,所以余数肯定是(n/2)
  2. 那么是否需要判断(n & 1)== 0那,因为一个正数乘2等于n,那么n肯定是偶数
  3. 满足上述两种情况的序列即是和为S的连续正数序列(即我们所要找的序列)
  • 接下来讨论一下序列长度的问题,我们可以从1一直遍历到S,或者可以一直遍历到(S/2),当然都可以。但是我们要想办法得出一个尽量小的区间长度,以避免时间的浪费。首先是最小长度,可由题目要求得出:n >= 2。然后是最大长度,为了让 n 尽可能大,我们让序列从 1 开始(从1开始肯定是最长的序列了) :即 (1 + n) * n / 2 == S, 那么 2S > n * n,故 n可求出 n 的最大值。举个例子,对于S= 100的情况,n的区间范围为[2,13]。显然区间范围更加精确了。
class Solution {
public:
	vector<vector<int> > FindContinuousSequence(int sum) {
		vector<vector<int>> vec;

		for (int n = (int)sqrt(2 * sum); n >= 2; n--)
		{
			if (((sum % n) * 2 == n) || ((n & 1) == 1 && sum % n == 0))
			{
				vector<int> v;
				for (int j = 0, k = sum / n - (n - 1) / 2; j < n; j++, k++)
				{
					v.push_back(k);
				}
				vec.push_back(v);
			}
		}
		return vec;
	}
};

 想法:

对于解法一肯定是自己想的,解法二和三来自于牛客讨论区,在此做个记录方便日后查阅。

另外对于解法三,前期想过使用等差数列来计算和的问题,但是由于分情况没有分清楚,故没有深入下去。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值