剑指Offer-44-和为S的两个数字

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/dawn_after_dark/article/details/81812537

题目

输入描述

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

输出描述

对应每个测试案例,输出两个数,小的先输出。

解析

预备知识

首先,我们需要知道一个知识,就是对于一组总和相同的整数对集合,2个整数越靠近,乘积越大;相反,2个整数差越大,乘积越小。以下给出证明,若整数对为a,b,总和为s = a + b, 同时有另一对整数(a + m),(b - m),很明显他们的总和也是s。

a b = a b

( a + m ) ( b m ) = a b + ( b a ) m m 2

( a + m ) ( b m ) a b = ( b a ) m m 2 = ( b a m ) m

b a > m

( b a m ) m > 0

经过以上证明,可以发现(a + m)(b - m)的乘积一定大于ab,所以对于题目中输出两个数乘积最小的,只需从最小开始遍历递增的数组,那么第一组总和为S的整数对必为乘积最小的结果。

思路一

我们已经得出了只需从最小开始遍历,寻找一组整数对总和为S即可。那么当我们遍历到i时,常规的思路,可能是遍历剩余数组元素看看是否存在对应数使得他们的总和为S,但这样复杂度为O(n^2)。
既然时间都消耗在查找在另一半数字上了,那么我们可以采用空间换时间的做法。预遍历一半数组,把数组元素全部存在set中,再次遍历数组,对于当前元素i,判断集合中是否存在(S - i)即可。

    /**
     * 空间换时间
     * @param array
     * @param sum
     * @return
     */
    public ArrayList<Integer> FindNumbersWithSum3(int [] array, int sum) {
        ArrayList<Integer> result = new ArrayList<>();
        if(array == null || array.length < 2) {
            return result;
        }
        Set<Integer> sets = new HashSet<>();
        for(int i = 0; i < array.length; i++) {
           sets.add(array[i]);
        }
        for(int i = 0; i < array.length - 1; i++) {
           int num = sum - array[i];
           if(sets.contains(num)) {
               result.add(array[i]);
               result.add(num);
           }
        }
        return result;
    }

思路二

从思路一得出,我们只需高效的解决如何确定另一半是否在数组中即可。由于此题的数组是有序的,看到有序数组加上查找问题,肯定二分查找啊。所以废话不多说,快速手撸一个二分算法(二分查找简介,见这篇博客的预备知识):

    //不存在返回-1
    public int binarySearch(int[] array, int num) {
        int left = 0, right = array.length - 1;
        while(left <= right) {
            int mid = left + (right - left) / 2;
            if(array[mid] > num) {
                right = mid - 1;
            } else if(array[mid] < num) {
                left = mid + 1;
            } else {
                return mid;
            }
        }
        return -1;
    }

    /**
     * 二分查找
     * @param array
     * @param sum
     * @return
     */
    public ArrayList<Integer> FindNumbersWithSum(int [] array, int sum) {
        ArrayList<Integer> result = new ArrayList<>();
        if(array == null || array.length < 2) {
            return result;
        }
        for(int i = 0; i < array.length - 1; i++) {
            int num = sum - array[i];
            if(binarySearch(array, num) != -1) {
                result.add(array[i]);
                result.add(num);
                return result;
            }
        }
        return result;
    }

思路三

左右夹逼法。
left表示第一个数的索引,right表示第二个数的索引,初始:left = 0, right = array.length - 1
进行以下方法夹逼:

  1. 若left < right,开始循环
  2. 当 array[left] + array[right] > sum 时:查找区间应缩小为[left, right - 1],不可能为[left + 1, right],因为这样总和更大了。
  3. 当 array[left] + array[right] < sum 时:查找区间应缩小为[left + 1, right],不可能为[left, right - 1],因为这样总和更小了。
  4. 当 array[left] + array[right] == sum 时,即满足条件,结束循环
    /**
     * 左右夹逼
     * @param array
     * @param sum
     * @return
     */
    public ArrayList<Integer> FindNumbersWithSum2(int [] array, int sum) {
        ArrayList<Integer> result = new ArrayList<>();
        if(array == null || array.length < 2) {
            return result;
        }
        int left = 0, right = array.length - 1;
        while(left < right) {
            int temp = array[left] + array[right];
            if(temp < sum) {
                left++;
            } else if(temp > sum) {
                right--;
            } else {
                result.add(array[left]);
                result.add(array[right]);
                return result;
            }
        }
        return result;
    }

总结

有序 + 查找,可首选二分。之后可以进一步优化。

猜你喜欢

转载自blog.csdn.net/dawn_after_dark/article/details/81812537