和为s的连续正数序列

输入一个正整数 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()][]);
    }

}

在这里插入图片描述

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值