题目描述
小明很喜欢数学,有一天他在做数学作业时,要求计算出9~16的和,他马上就写出了正确答案是100。但是他并不满足于此,他在想究竟有多少种连续的正数序列的和为100(至少包括两个数)。没多久,他就得到另一组连续正数和为100的序列:18,19,20,21,22。现在把问题交给你,你能不能也很快的找出所有和为S的连续正数序列? Good Luck!
输出描述:
输出所有和为S的连续正数序列。序列内按照从小至大的顺序,序列间按照开始数字从小到大的顺序
解法一(感觉这种方法在面试时不会通过的):
通常看到这种题目,第一想法肯定是逐个遍历。
两个指针,i,j = i + 1:
- 当从i 到 j的和小于sum时,j 右移
- 当从i 到 j的和大于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
- 稍微解释一下,因为小数位是0.5,所以余数肯定是(n/2)
- 那么是否需要判断(n & 1)== 0那,因为一个正数乘2等于n,那么n肯定是偶数
- 满足上述两种情况的序列即是和为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;
}
};
想法:
对于解法一肯定是自己想的,解法二和三来自于牛客讨论区,在此做个记录方便日后查阅。
另外对于解法三,前期想过使用等差数列来计算和的问题,但是由于分情况没有分清楚,故没有深入下去。