输入一个正整数 target ,输出所有和为 target 的连续正整数序列(至少含有两个数)。
序列内的数字由小到大排列,不同序列按照首个数字从小到大排列。
示例 1:
输入:target = 9
输出:[[2,3,4],[4,5]]
示例 2:
输入:target = 15
输出:[[1,2,3,4,5],[4,5,6],[7,8]]
限制:
1 <= target <= 10^5
来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/he-wei-sde-lian-xu-zheng-shu-xu-lie-lcof
方法一:暴力解决:
class Solution {
public int[][] findContinuousSequence(int target) {
int i,j,k,sum;
int[] tmp;
/*
由于最后返回的数组的大小不定,所以定义一个列表,并且它的泛型的类型
是一个数组,这样将list转成数组的时候是一个二维数组了
*/
List<int[]> list = new ArrayList<int[]>();
for(i = 1; i < target; i++){
sum = i;
for(j = i + 1; j < target; j++){
sum += j;
if(sum == target){
//如果sum等于target,那么就将i - j的元素添加到数组中
tmp = new int[j - i + 1];
for(k = i; k <= j; k++){
tmp[k - i] = k;
}
list.add(tmp);
break;
}else if(sum > target) break;//如果sum已经大于target了,那么直接退出循环
}
}
return list.toArray(new int[list.size()][]);
}
}
方法二:利用数学方法
我们知道这些数是一个等差数列,并且公差d = 1(即每一个相邻的数相差1),因此我们可以明白两个数之间的和为(x + y) * (y - x + 1) / 2,其中(y - x + 1)表示x-y之间一共有多少个数。比如x = 1,y = 3,那么1 + 2 + 3 = (1 + 3) * ( 3 - 1 + 1) / 2 = 6.
所以从i = 1开始遍历(i作为x),那么只要判断(j + i) * (j - i + 1) / 2 = target这个一元二次方程(即j² + j + i - i² - 2 * target = 0)是否成立即可,如果成立,并且方程的节点j大于i,那么只要将i-j的数添加到数组中即可,否则不需要做任何操作。
class Solution {
public int[][] findContinuousSequence(int target) {
int i,k,q,y;
long t;
int[] tmp;
List<int[]> list = new ArrayList<int[]>();
for(i = 1; i < target; i++){
t = 1 - 4 * (i - (long)i * (long)i - 2 * target);//由于target的范围是1 <= target <= 10 ^5,所以i平方可能超出int范围
if(t < 0)
continue;//如果t小于0,那么不存在解
else{
//存在解,那么取解中的最大值
q = (int)Math.sqrt(t);
if((long)q * (long)q == t){ //判断是否为整数解,这里同样可能存在q * q大于int的范围,所以转成了long
y = (-1 + q) / 2;
if(y <= i) //如果y小于等于i,那么不用做任何操作(因为要求至少2个数)
continue;
tmp = new int[y - i + 1];
for(k = i; k <= y; k++){
tmp[k - i] = k;
}
list.add(tmp);
}
}
}
return list.toArray(new int[list.size()][]);
}
}
运行结果:
方法三:双指针
注意,这里的双指针并不是从左右开始遍历的,假设i = 1,j = target - 1,那么如果i-j所有数字的和sum等于target,那么就将i-j之间的所有数字添加到数组中,否则如果sum大于target,那么j需要往左移,因为sum已经大于target了,那么如果i往右移只会是sum更大,同理,如果sum小于target,那么需要将i往右移,因为如果将j往左移只会使sum更小。看起来,思路是正确的,但是请看下面的例子可以发现这种思路是错误的:
正确的思路:
①从同一端开始出发,其中初始值为:i = 1,j = 2,sum = 3。之所以i = 1,j = 2,sum = 3,是因为题目要求返回的数组至少含有两位数,所以target最小是3,从而可以退出i、j、sum的初始值。
②如果sum等于target,那么就需要将i-j的所有数字添加到数组中。
如果sum大于等于target,那么需要先使sum减去原来的i,然后移动i(即i++),否则,sum小于target,那么需要增加sum,所以j++,然后sum += j.
由此我们可以知道,j右移是为了使sum变大,而i右移是为了调整,如果还不是很明白,请看下面的例子:
class Solution {
public int[][] findContinuousSequence(int target) {
int i,j,k,sum;
int[] tmp;
i = 1;
j = 2;
sum = 3;//一开始初始值,i = 1,j = 2,sum = 3,是因为题目要求至少含有两个数,所以target最小是3,从而i的初始值为1,j为2,sum为3
List<int[]> list = new ArrayList<int[]>();
//双指针进行求解
while(i < j){
//当i大于等于j的时候退出循环,因为需要保证至少含有两个数字
if(sum == target){
//如果等于target,那么就将i-j数字添加到数组中
tmp = new int[j - i + 1];
for(k = i; k <= j; k++)
tmp[k - i] = k;
list.add(tmp);
}
if(sum >= target){
//如果当前元素大于等于target,那么需要寻找下一组的连续整数,所
//以先调整sum,然后更新i的值
sum -= i;
i++;
}else{
//如果sum小于target,那么将j往右移,然后再加上j
j++;
sum += j;
}
}
return list.toArray(new int[list.size()][]);
}
}