剑指 Offer 57 - II. 和为s的连续正数序列 + 双指针 + 数论
剑指 Offer 57 - II. 和为s的连续正数序列
Offer_57_2
题目描述
方法一:暴力枚举
package com.walegarrett.offer;
/**
* @Author WaleGarrett
* @Date 2021/2/12 16:42
*/
/**
* 题目描述:输入一个正整数 target ,输出所有和为 target 的连续正整数序列(至少含有两个数)。
* 序列内的数字由小到大排列,不同序列按照首个数字从小到大排列。
*/
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
/**
* 方法一:暴力枚举
*/
public class Offer_57_2 {
public int[][] findContinuousSequence(int target) {
List<int[]> list = new ArrayList<>();
int sum = 0, upp = (target - 1) / 2;
for(int i=1; i<= upp; i++){
for(int j=i;;j++){
sum+=j;
if(sum > target) {
sum = 0;
break;
}
else if(sum == target){
int[] ans = new int[j-i+1];
for(int k=i;k<=j;k++){
ans[k-i] = k;
}
list.add(ans);
sum = 0;
break;
}
}
}
return list.toArray(new int[list.size()][]);
}
}
方法二:枚举+数学优化
/**
* 方法二:枚举+递增序列的求和公式
*/
class Offer_57_2_2 {
public int[][] findContinuousSequence(int target) {
List<int[]> list = new ArrayList<>();
int sum = 0, upp = (target - 1) / 2;
for(int x=1; x<= upp; x++){
long delta = 1 - 4 * (x - (long) x * x - 2 * target);
if(delta<0)//无解
continue;
int delta_sqrt = (int)Math.sqrt(delta + 0.5);
if((long)delta_sqrt * delta_sqrt == delta && (delta_sqrt-1)%2 == 0){
int y = (-1 + delta_sqrt) / 2;
if(x<y){
int[] ans = new int[y-x+1];
for(int k=x;k<=y;k++){
ans[k-x]=k;
}
list.add(ans);
}
}
}
return list.toArray(new int[list.size()][]);
}
}
方法三:滑动窗口
本题是滑动窗口的典型题目,题目的核心就是移动一个非固定数目的窗口,通过移动左右边界来寻找和刚好等于target的子数组。
这里可以通过设置一个【左闭右开】的窗口,初始时,左右指针都指向1,边界条件是左指针小于等于target的一半。之所以这么设置,是因为题目要求子数组中至少有两个数组,所以将左指针限制在target/2,可以保证数组中最少都有两个数字。
后续的窗口移动主要是通过设置一个sum值,如果sum < target,则表明窗口的和太小了,此时需要移动右指针来增加窗口的元素;如果sum < target,则表明窗口的和太大了,此时需要右移左指针进行窗口的缩小来减少窗口的元素;如果sum == target,则表示找到了符合条件的子数组,记录下来。这里需要注意的一点是,sum == target时,也需要移动左指针,因为此时最左边的元素一定不会再次被使用了,此时需要缩小窗口才能让窗口继续向右移动。
下面给出java的实现代码:
class Solution {
public int[][] findContinuousSequence(int target) {
int left = 1, right = 1;
int half = target / 2;
int sum = 0;
List<int[]> res = new LinkedList<>();
// 至少包含两个数,左闭右开区间
while (left <= half) {
if (sum < target) {
sum += right;
right++;
} else if (sum == target) {
// 左闭右开区间
int[] arr = new int[right - left];
for (int i=left; i<right; i++) {
arr[i-left] = i;
}
res.add(arr);
// 缩小左边界
sum -= left;
left++;
} else {
// 缩小左边界
sum -= left;
left++;
}
}
return res.toArray(new int[res.size()][]);
}
}
参考题解:和为s的连续正数序列
Either Excellent or Rusty