剑指 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的连续正数序列

posted @ 2021-02-12 17:38  Garrett_Wale  阅读(64)  评论(0编辑  收藏  举报