题目
输入描述
小明很喜欢数学,有一天他在做数学作业时,要求计算出9~16的和,他马上就写出了正确答案是100。但是他并不满足于此,他在想究竟有多少种连续的正数序列的和为100(至少包括两个数)。没多久,他就得到另一组连续正数和为100的序列:18,19,20,21,22。现在把问题交给你,你能不能也很快的找出所有和为S的连续正数序列? Good Luck!
输出描述
输出所有和为S的连续正数序列。序列内按照从小至大的顺序,序列间按照开始数字从小到大的顺序
解析
预备知识
题目的意思其实就是给定一个sum,求有多少个长度大于1的连续序列的和等于20。
比如输入20, 有1个序列满足条件: 2 3 4 5 6
.
很常规很暴力的做法,就是从1开始遍历,然后对于1作为序列的首部形成的序列判断是否满足条件,之后继续从2作为序列的首部判断是否满足条件。这样复杂度为O(n^2)。而且我们每次都要重新计算序列和,这里我们完全可以利用上一次的结果。
思路一
我们定义一把尺子,该尺子长度动态变化,它代表了我们要求的满足条件的序列。尺子在动态变化的时候维护一个总和。
1. 初始时,尺子的长度为0,总和为0
2. 我们开始向右拓展一位,并更新总和,就这样不断向后扩展,它可能碰到2种情况。当尺子总和的值大于给定的目标值target时,转第3步。当尺子的总和正好等于target,转第四步
3. 因为尺子总和大于target了,所以我们应该缩短尺子。我们从尺子左端缩短一格,因为以当前左端形成的尺子是不满足条件,所以我们要削掉这一块。就这样不断的削掉尺子左端,直到尺子的总和小于等于target
4. 尺子的总和正好等于target,表明满足条件,所以保存当前尺子。因为以当前尺子左端开头的尺子在向后扩展肯定大于target,故要先削掉左端,转第二步继续扩展。
上面步骤可以用下图表示(仅含关键部分,其他类似)
因为题目要求了尺子长度大于1,mid + mid + 1 > target,所以我们的尺子只需扩展到mid(target的一半)时即可。
/**
* 尺取法
* @param sum
* @return
*/
public static ArrayList<ArrayList<Integer>> FindContinuousSequence(int sum) {
ArrayList<ArrayList<Integer>> result = new ArrayList<>();
if(sum == 1) {
return result;
}
//定义尺子
ArrayList<Integer> temp = new ArrayList<>();
int tempSum = 0;
/**
* 因为尺子最短长度为2,又因为mid + mid + 1 > sum
* 所以只需遍历到sum的一半即可
*/
int mid = (sum + 1) / 2;
for(int i = 1; i <= mid; i++) {
//尺子向后扩展
tempSum += i;
temp.add(i);
//如果当前尺子的总和大于sum, 那么就需要削掉尺子的左端
if(tempSum > sum) {
//直到尺子的总和小于等于sum为止
while(tempSum > sum) {
tempSum -= temp.get(0);
temp.remove(0);
}
}
/**
* 如果尺子的总和等于sum,则为目标尺子,添加到结果集中
* 削掉尺子左端一个,继续向后考察
*/
if(tempSum == sum) {
ArrayList<Integer> item = new ArrayList<>();
for(int t : temp) {
item.add(t);
}
result.add(item);
tempSum -= temp.get(0);
temp.remove(0);
}
}
return result;
}
思路二
思路二其实就是对思路一在程序上的改进了,因为个人比较喜欢精简的代码,所以这里利用2个指针一个表示尺子的左端,一个表示尺子的右端,来避免ArrayList频繁的增删操作,毕竟会设计到数组的移动操作!。
/**
* 改进尺子
* @param sum
* @return
*/
public static ArrayList<ArrayList<Integer>> FindContinuousSequence2(int sum) {
ArrayList<ArrayList<Integer>> result = new ArrayList<>();
if(sum <= 2) {
return result;
}
int left = 1, right = 2, tempSum = 3;
while(left < right) {
if(tempSum < sum) {
right++;
tempSum += right;
} else if(tempSum > sum) {
tempSum -= left;
left++;
} else {
ArrayList<Integer> temp = new ArrayList<>();
for(int i = left; i <= right; i++) {
temp.add(i);
}
tempSum -= left;
left++;
}
}
return result;
}
总结
连续序列之类的题目可以利用尺取法思想来做(个人理解),其实也可以把它理解为TCP的滑动窗口,都是相同的。