文章目录
1、题目描述2、解题思路2.1 穷举法2.2 前N项和优化2.3 滑动窗口法
3、解题代码3.1 穷举法3.2 前N项和优化3.3 滑动窗口法
4、解题心得
1、题目描述
【JZ41】小明很喜欢数学,有一天他在做数学作业时,要求计算出9~16的和,他马上就写出了正确答案是100。但是他并不满足于此,他在想究竟有多少种连续的正数序列的和为100(至少包括两个数)。没多久,他就得到另一组连续正数和为100的序列:18,19,20,21,22。现在把问题交给你,你能不能也很快的找出所有和为S的连续正数序列? Good Luck! 知识点:数学,前缀和,滑动窗口 难度:☆
2、解题思路
2.1 穷举法
首先分析出题意关键词:正数、连续。 我们可以通过确定左边界和右边界的方式来穷举出最终结果。 步骤: 1、定义左边界i,初始值为1,不超过S的一半; 2、定义右边界j,初始值为i+1; 3、计算序列[i,i+1,i+2,...,j]每个元素的和并判断与S的关系; 3.1 如果等于S,则找到一组满足要求的序列。继续找下一组,左边界+1,回到第2步; 3.2 如果小于S,则序列还可以拓展,右边界+1,回到第3步; 3.3 如果大于S,则序列已经不符合要求了,重新确定左边界,左边界+1,回到第2步;
2.2 前N项和优化
穷举法有三层嵌套的for循环,时间复杂度为O(N3)。 其中最里面一层for循环是计算[i,i+1,i+2,...,j]的元素和,当右边界+1,则再计算[i,i+1,i+2,...,j+1]的元素和,其中的[i,i+1,i+2,...,j]又计算了一次,属于重复计算。 我们可以把这一部分存储到变量temp,然后右边界+1时,我直接加上j+1即可。
2.3 滑动窗口法
把左边界i和右边界j包含的区域看成是一个窗口。 整个窗口从左往右移动,也就是说,我要扩大窗口时,只能右边界+1,要缩小窗口时,则是左边界+1。 算法步骤如下: 1、开始时,左边界i=1,右边界j=2; 2、计算序列和[i,i+1,...,j]保存到temp中; 3、判断temp和S的关系: 3.1 如果temp<S,说明窗口过小,需要扩大窗口,先j++,然后temp更新为temp+j; 3.2 如果temp>S,说明窗口过大,需要缩小窗口,先temp更新为temp-i,然后i++; 3.3 如果temp==S,则找到符合要求的序列,保存。然后temp先更新为temp-i,左边界再+1,继续寻找下一组合适的序列。 4、当i大于S的一半时,算法结束。
3、解题代码
3.1 穷举法
package pers
.klb
.jzoffer
.medium
;
import java
.util
.ArrayList
;
public class ContinuousSequence {
public ArrayList
<ArrayList
<Integer>> FindContinuousSequence(int sum
) {
if (sum
< 2) {
return new ArrayList<ArrayList
<Integer>>();
}
ArrayList
<ArrayList
<Integer>> lists
= new ArrayList<ArrayList
<Integer>>();
for (int i
= 1; i
<= sum
/ 2; i
++) {
for (int j
= i
+ 1; j
< sum
; j
++) {
int temp
= 0;
for (int k
= i
; k
<= j
; k
++) {
temp
+= k
;
}
if (temp
== sum
) {
ArrayList
<Integer> list
= new ArrayList<Integer>();
for (int k
= i
; k
<= j
; k
++) {
list
.add(k
);
}
lists
.add(list
);
} else if (temp
> sum
) {
break;
}
}
}
return lists
;
}
}
时间复杂度:O(N3) 空间复杂度:O(1)
3.2 前N项和优化
package pers
.klb
.jzoffer
.medium
;
import java
.util
.ArrayList
;
public class ContinuousSequence {
public ArrayList
<ArrayList
<Integer>> FindContinuousSequence(int sum
) {
if (sum
< 2) {
return new ArrayList<ArrayList
<Integer>>();
}
ArrayList
<ArrayList
<Integer>> lists
= new ArrayList<ArrayList
<Integer>>();
int temp
;
for (int i
= 1; i
<= sum
/ 2; i
++) {
temp
= i
;
for (int j
= i
+ 1; j
< sum
; j
++) {
temp
+= j
;
if (temp
== sum
) {
ArrayList
<Integer> list
= new ArrayList<Integer>();
for (int k
= i
; k
<= j
; k
++) {
list
.add(k
);
}
lists
.add(list
);
} else if (temp
> sum
) {
break;
}
}
}
return lists
;
}
}
时间复杂度:O(N2) 空间复杂度:O(1)
3.3 滑动窗口法
package pers
.klb
.jzoffer
.medium
;
import java
.util
.ArrayList
;
public class ContinuousSequence {
public ArrayList
<ArrayList
<Integer>> FindContinuousSequence(int sum
) {
if (sum
< 2) {
return new ArrayList<ArrayList
<Integer>>();
}
ArrayList
<ArrayList
<Integer>> lists
= new ArrayList<ArrayList
<Integer>>();
int i
= 1;
int j
= 2;
int temp
= i
+ j
;
while (i
<= sum
/ 2) {
if (temp
< sum
) {
j
++;
temp
+= j
;
} else if (temp
> sum
) {
temp
-= i
;
i
++;
} else {
ArrayList
<Integer> list
= new ArrayList<Integer>();
for (int k
= i
; k
<= j
; k
++) {
list
.add(k
);
}
lists
.add(list
);
temp
-= i
;
i
++;
}
}
return lists
;
}
}
时间复杂度:O(N) 空间复杂度:O(1)
4、解题心得
本题是很经典的有序序列处理的问题,第一反应是穷举,穷举后的首要优化的地方就是重复计算,有迭代的意思在里面。 滑动窗口法有个特点就是前面的符合要求的序列的元素个数肯定比后面的少。