和为S的两个数字,编程题解法思路

题目描述

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

题目分析

  任何一道编程题,拿到题目都是从题干入手,思考题干的信息向我们透露出什么含义。这道题的题干中最有用的信息就是就是递增序列。显然,如果不把这个递增用上的话,就无异于是暴力查找即便是普通的查找,需要找到满足特定条件的两个数最坏情况复杂度是 O ( n 2 ) O(n^2) ,在其他时候接触到编程题目需要分析复杂度这一点也是要清楚的,过于暴力的方法(及不改变复杂度的优化)在此不赘述。
  题干中还提到如果有多组,输出数组中乘积最小的,可以证明,如果两个数中较小者在多组数中最小,则这组数的乘积最小,以下有详细证明,可(不)以(感)理(兴)解(趣)的直接跳过。

乘积最小证明

   S a S a a f 已知两个数之和为S,不妨设较小者为a,则另一个数为S-a,求证a越小则两个数乘积f越小
   f = a ( S a ) = a 2 + a S f=a(S-a)=-a^2+aS
   f 乘积f是关于 a 1 0 S 2 的二次函数,因为二次项系数为-1小于0,则二次函数开口向下,对称轴为\frac{S}{2}
a S a a S 2 a f a a f 又\because a≤S-a,\therefore a≤\frac{S}{2},故a位于二次函数的对称轴左边,所以f是关于a的增函数,即a越小,则f越小

暴力解法优化 O ( n l g n ) O(nlgn)

  暴力解法之所以暴力是在于没有将数组递增这个条件用上,以至于在查找的过程中无法根据查找的相关性来减少查找次数,首先知道就是两个数之和是S,所以两个数是有相关性的,如果我们查找到第一个数a,那么第二个数我们就知道是 S a S-a 了,所以我们直接去判断 S a S-a 是否在数组中即可。
  我们要判断 S a S-a 是否在数组中,此时需要用到数组是递增的特点,相当于有序数组的查找,显然使用二分查找可以降低复杂度,同时,在编写代码的时候,第一个数字a我们不确定,只能从前往后(从小往大)遍历,在二分查找 S a S-a 时,前面的数组已经不需要遍历了,所以查找的时候,只需要从当前数字的后面到数组末尾进行二分查找即可。找到的第一组数字其中较小者肯定是最小的,所以该组数字乘积肯定是最小的。
  核心代码是一个循环加二分查找,代码过于简单,就不需要写了。当然查找可以比较一下第一个数找到 S / 2 S/2 就可以不继续往下找了,这些都是小技巧。

技巧解法 O ( n ) O(n)

  最好的解法一定是把所有题干信息都用到了,而且往往还要用到一定的特殊技巧。至于特殊技巧的部分可以当成定式然后去记忆,遇到类似的题目能够套用即可。
  本题的特殊技巧是双指针,具体的操作流程是这样:判断两个指针如果不重合则一直循环,每次循环的过程是一个指针在数组中从前往后扫描,一个从后往前,如果两个指针指向的数字之和小于S,则前面的指针往后(大)移,如果两数之和大于S,则后面的指针往前(小)移动,如果两数之和等于S,则返回。
  很多人对这个流程的原理可能不理解,下面我给出理由,同样,理解的可以跳过。

双指针原理探究

  最差情况下,两边指针重合,相当于把整个数组都扫描了一遍,复杂度为 O ( n ) O(n) 。如果存在两个数和为S,则扫描整个数组,一定可以找到其中的一个,不失一般性,假设先找到的是两个数中较小者,较小者指针为i,较大者指针为j。先找到较小者此时i指针确定,因为较大者的指针是从大向小扫描,则还没找到,此时j指向的数字一定是比较大者大的,然后j不断的减1直到找到该数字。为什么j指向的数字一定是比较大者大呢,因为如果j指向的数字比较大者小的话,那么与我们假设的先找到较小的数字矛盾。如果较大指针指向的数比较大数大,那么较大指针会持续减小,直到等于较大数。同样,先找到较大者也是一样的道理。
  再看乘积最小的条件,之前证明过较小的数越小,则乘积越小,因为两数之和是相等的,同样说明较大的数越大,则乘积越小,乘积越小的两个数越是靠两边的,所以不管是较小的数还是较大的数总是比其他的一组数发现的更早。
  综上,如果存在正确答案一定会找到,且满足乘积最小。

C++代码实现

class Solution {
public:
	vector<int> FindNumbersWithSum(vector<int> array, int tsum) {
		int i = 0, j = array.size() - 1;
		while (i < j) {
			i += (array[i] + array[j] < tsum);
			j -= (array[i] + array[j] > tsum);
			if (array[i] + array[j] == tsum)break;
		}
		vector<int>res;
		if (i < j) {
			res.push_back(array[i]);
			res.push_back(array[j]);
		}
		return res;
	}
};

  最后再举一个用到这个方法思路的题目,大家可以自行思考。给定一个包含 n 个整数的数组 nums,判断 nums 中是否存在三个元素 a,b,c ,使得 a + b + c = 0 ?找出所有满足条件且不重复的三元组。注意:答案中不可以包含重复的三元组。

发布了36 篇原创文章 · 获赞 4 · 访问量 47万+

猜你喜欢

转载自blog.csdn.net/m0_38065572/article/details/104129263