题目描述(剑指57.2)
输出所有和为 S 的连续正数序列。例如和为 100 的连续序列有:
[9, 10, 11, 12, 13, 14, 15, 16]
[18, 19, 20, 21, 22]。
解题思路
1、滑动窗口
滑动数组:可看成数组中框起来的一个部分。为编程方便,设置为左闭右开区间。
初始值:i=1,j=1,滑动窗口位于序列最左侧,窗口大小为0。
性质 :窗口的左边界和右边界永远只能向右移动,而不能向左移动。
过程:
定义:窗口内几个数和sum,左端点 i,右端点 j。
窗口移动与结果记录:
2.1. if sum < target,sum 需要增加,即扩大窗口,右边界右移,j++;
2.2. if sum > target,sum 需要减少,即缩小窗口,左边界右移,i++;
2.3. if sum = target,记录此时结果,即记下[i, j)的元素。这也是唯一一个i开头的序列。 接下来,左边界右移,i++;
遍历完毕,返回结果集。
问题:窗口移动能找到全部解吗?
分析:
-
(1) 1 + 2 +...+ 8 < target
-
(2) 1 + 2 +...+ 8 + 9 > target
-
由(1)(2)可得,说明 1 开头的序列找不到解。
-
(3)寻找以2开头的序列:2+...+8 < 1+2+...+8 < target. 2+3+...+8+9 ? target,如果两端相等,则记录下该序列,然后左边界右移。 结论:该2开始的序列唯一。 进一步推论,每个元素开始的序列都有判断,如果与target相等,均唯一,所以能找到全部解。
代码块
class Solution {
public int[][] findContinuousSequence(int target) {
int i = 1; // 滑动窗口的左边界
int j = 1; // 滑动窗口的右边界
int sum = 0; // 滑动窗口中数字的和
List<int[]> res = new ArrayList<>();
while (i <= target / 2) {
if (sum < target) {
// 右边界向右移动
sum += j;
j++;
} else if (sum > target) {
// 左边界向右移动
sum -= i;
i++;
} else {
// 记录结果
int[] arr = new int[j-i];
for (int k = i; k < j; k++) {
arr[k-i] = k;
}
res.add(arr);
// 左边界向右移动
sum -= i;
i++;
}
}
return res.toArray(new int[res.size()][]);
}
}
public ArrayList<ArrayList<Integer>> FindContinuousSequence(int sum) {
int start = 1, end = 2;
int curSum = 3;
while (end < sum) {
if (curSum > sum) {
curSum -= start;
start++;
} else if (curSum < sum) {
end++;
curSum += end;
} else {
ArrayList<Integer> list = new ArrayList<>();
for (int i = start; i <= end; i++)
list.add(i);
ret.add(list);
curSum -= start;
start++;
end++;
curSum += end;
}
}
return ret;
}