剑指offer-41-和为S的两个数字--和为S的连续整数序列

24 篇文章 0 订阅

题目一

输入一个递增排序的数组和一个数字S,在数组中查找两个数,
使得他们的和正好是S,如果有多对数字的和等于S,
输出两个数的乘积最小的(外层成绩小于内层)。

    看到这个问题我们可以想到的最简单的方法先固定一个数字,然后依次和n - 1个数字判断是不是和为S,但是这样时间复杂度比较高为O(n^2)。
    可以使用下面这种方法来解决来解决。

接下来看一下解题思路:
    首先定义两个下标索引,
* 第一个指向数组的第一个,
* 第二个指向数组的最后一个,
* 如果加起来和大于给定值,指向最后一个数的指针向前移;
* 如果加起来和小于给定值,指向第一个数的指针向后移;
* 如果存在这样一对数字,保存并返回true,
* 否则返回false
代码示例:

public boolean findTwoNumbersSumIsValue(int[] array, int sum, int[] num1, int[] num2) {
        if (array == null || array.length <= 0) {
            return false;
        }
        boolean result = false;
        int start = 0;
        int end = array.length - 1;

        while (start < end) {
            if (array[start] + array[end] < sum) {
                start++;
            } else if(array[start] + array[end] > sum) {
                end--;
            } else {
                num1[0] = array[start];
                num2[0] = array[end];
                result = true;
                break;
            }
        }
        return result;
}
总结

    因为数组是排序的,所以较大的数一直在较小的数的后面,所以这里用while (start < end)判断。代码中从两端向中间扫码数组,所以时间复杂度为O(n)

题目二

输入一个整数S,打印出所有和为S的连续整数序列,至少包含两个数
思路一

    穷举法
* 从1开始累加,遇到和为S的就加入到列表中,
* 因为序列是相差为1的连续序列,所以
* 当累加到(S / 2 + 1)时退出循环

代码示例:

public ArrayList<ArrayList<Integer>> FindContinuousSequence(int sum) {
        if (sum <= 0) {
            return null;
        }
        ArrayList<ArrayList<Integer>> result = new ArrayList<>();
        ArrayList<Integer> sequence;
        //因为找的序列最少为2个数字,所以只有找到sum / 2 + 1就可以了
        for (int i = 1; i < (sum / 2 + 1); i++) {
            int res = 0;
            int j = i;
            //初始化序列队列
            sequence = new ArrayList<>();
            //如果序列和小于给定值
            while (res < sum) {
                res += j;
                sequence.add(j);
                j++;
            }
            //如果序列和等于给定值
            if (res == sum) {
                //将序列列表增加到结果列表里
                result.add(sequence);
            } else {
                //序列作废重新找
                sequence.clear();
            }
        }

        return result;
}
总结

    使用这种方法的时间复杂度比较大。每次序列求和的时候如果条件满足就直接加到列表中,若是条件不满足序列就作废,需要重新从上一次的位置的下一个重新开始计算(比如第一次从1开始计算序列和,第二次就继续返回从2 重新计算)这样比较浪费时间。可以用下面这种方法在前一个序列和的基础上继续计算,不用回去重新计算。

思路二

    其实这道题可以根据上面那道题的思想来做。
用两个数left和right分别表示序列的最小值和最大值,
* 初始值让left = 1; right = 2;
* 如果序列left到right的和大于S,去掉最小值(left++),
* 否则增加最大值(right++),
* 如果相等则将该序列加入到列表
代码示例:

public ArrayList<ArrayList<Integer>> FindContinuousSequence1(int sum) {
        if (sum <= 0) {
            return null;
        }
        //用于存储序列的列表
        ArrayList<ArrayList<Integer>> result = new ArrayList<>();
        //最小值
        int left = 1;
        //最大值
        int right = 2;

        while (left < right) {
            //等差数量求和S = (a1 + an) * n / 2;
            int res = (left + right) * (right - left + 1) / 2;
            //序列和与给定值相等
            if (res == sum) {
                ArrayList<Integer> sequence = new ArrayList<>();
                //将该序列增加到序列队列里
                for (int i = left; i <= right; i++) {
                    sequence.add(i);
                }
                result.add(sequence);
                //减少最小值(将序列右移)
                left++;
            //序列和大于给定值
            } else if (res > sum) {
                //去掉最小值
                left++;
            //序列和小于给定值
            } else {
                //增加最大值
                right++;
            }
        }
        return result;
    }
总结

     通常通过循环求一个序列和,但是考虑到每一次操作之后的序列和操作之前的序列相比大部分是一样的,只是增加或者减少了一个数字,因此可以在前一个序列的和的基础上求操作之后的序列的和。这样就可以减少不必要的计算。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值