排序数组中的两个数字之和 | 循序递进---@二十一画

题目:

给定一个已按照 升序排列 的整数数组 numbers ,请你从数组中找出两个数满足相加之和等于目标数 target 。

函数应该以长度为 2 的整数数组的形式返回这两个数的下标值。numbers 的下标 从 0 开始计数 ,所以答案数组应当满足 0 <= answer[0] < answer[1] < numbers.length 。

假设数组中存在且只存在一对符合条件的数字,同时一个数字不能使用两次。

示例 1:

输入:numbers = [1,2,4,6,10], target = 8
输出:[1,3]
解释:26 之和等于目标数 8 。因此 index1 = 1, index2 = 3
示例 2:

输入:numbers = [2,3,4], target = 6
输出:[0,2]
示例 3:

输入:numbers = [-1,0], target = -1
输出:[0,1]

提示:

2 <= numbers.length <= 3 * 104
-1000 <= numbers[i] <= 1000
numbers 按 递增顺序 排列
-1000 <= target <= 1000
仅存在一个有效答案

分析:

这道题目很是类似我之前发过的题目-两数之和

其中有几种思路可解此题:

  1. 暴力法【双重循环一一相加求解】
  2. 记录法【hashMap寻找 目标数】
  3. 指针法【指针双夹,寻找结果】

代码:

第一版:暴力法

预定义:外循环指针i,内循环指针j,给定值target,给定数组arr

初始化:i=0,j=i+1,len=arr.length-1

step1:外循环i∈[0,len] , 内循环j∈[i+1,len],判断arr[i]+arr[j]=target? 如果满足则返回ij

step2:如果不满足上述条件,那么继续循环,直到循环完成,因为问题定义肯定有解,所以在循环结束前一定可以找到满足条件的值。

class Solution {
    public int[] twoSum(int[] numbers, int target) {
        int i = 0;
        int j = 1;
        int len = numbers.length-1;
        for(int i=0;i<=len;i++){
            for(int j=i+1;j<=len;j++){
                if(numbers[i]+numbers[j]==target){
                    return new int[]{i,j};
                }
            }
        }
    }
}
第二版:记录Hash法

题目中要求寻找ab,使得a+b满足target,也就是寻找数据关系。

1)使用暴力法寻找满足关系的时候只能一个一个元素去遍历,举例:前提在数组arr中,给一个a(a in arr),问有没有一个ba加起来是target的?因为不了解数组中的元素,所以只能一个一个去寻找可能是b的每一个元素。 也就是,给你一个数组arr,问你arr中有没有5呀,你当然不知道了,只能一个一个去找了呗。

2)如果有一个容器,可以直接告诉你里面是否存在某个元素,是不是就快多了。举例:前提在容器vec中,给一个a(a in arr),问有没有一个ba加起来是target的?因为了解容器中的每一个元素,所以直接找(target-a)是否存在vec中,vec直接就返回了是或否。这样就快多了。这样的容器其实有很多,比如setmap等等。

3)我们发现,vec存在b,我们找到答案了,我们要返回结果呀是不,所以我们如果使用set了,发现set里面只存储了元素,不对呀。返回结果不要元素,要下标呀,假如可以把下标和元素值一起存储就好了。。。。这个时候,map就用到了,map天然是k-v模式,恰好可以存储下标对应的值,如果值b符合a+b=target,那么直接返回值b的所对应的下标value即可。(为什么a的下标不用从map中返回呢?因为a此时是遍历的,遍历过程a的下标是被循环所记录的,直接返回当前循环体中的递增值i即可)

4)所以我们需要先把数组存储转化为map存储,然后在遍历数组中每一个元素a,寻找map中是否有匹配的元素b

哈希表的方法,确实快多了。但是我们遍历了两次arr,同样map的put了n次,get了n次。有什么方法可以减少我们的运行次数呢?

6)我们之所以选择遍历两次arr,是因为如果开始没有将全部元素都put到map中,那么在遍历a的时候,万一存在b满足a+b=target,但是因为此时b还没加载到map里面,导致没有及时返回正确,并a此时也遍历结束跳过了,岂不是凉凉哉。。。

7)😺我们来看下哈,方案1中确实会发生这样的现象,因为如果想要没有全部加载完元素就开始寻找答案,是有可能跳过a的,但是想一下,如果a被跳过了,a就不会返回了吗?当我们终于遍历到b的时候,map里面有a的呀!!!!那么此时b可以找到a搭伙满足a+b=target的。所以a还是会被返回的。

8)结论:可以一遍put元素一遍寻找答案,只要存在答案,那么一定会在arr遍历前被返回。因为返回的答案是组合,a和b,我们不仅可以拿着a去找b,也可以拿着b去找a。只要两个元素在同一个地方,那么不管中途是否错过,只要元素满足数据关系,最后一定会找到对的那个它。

特别注意点:

1)如果遇到map中有重复的值,那么需要将已存在重复值覆盖掉吗?

解:题目中是这样描述的:“你可以假设每种输入只会对应一个答案。但是,数组中同一个元素在答案里不能重复出现”,所以我们可以判断出如果在遍历的过程出现了重复元素,我们记作r,且有另外一个元素的值和r的值是一样的,我们记作r1,(两个元素在数组中的前后顺序这里不需要讨论,我们假设r是先存储在map中的元素,r1是后遍历到的元素)。我们判断是否可以覆盖或者不可以覆盖的根本标准是,覆盖了r会不会影响结果?如果返回里面没有r或者r1,那么覆盖了也不影响最后返回值,如果结果中存在r或者r1,那么覆盖就会影响结果,就不可以覆盖了~

如下判断是否影响结果:

①如果存在r+b = target,那么一定存在r1+b=target,存在了两个答案,所以此时与题意不符。也即是重复元素r不可以满足(r=a || r=b ) ,一旦满足就违背了题目要求。

②那么如果(r=a && r1=b)呢,如果存在r+r1=target,此时和题意符合。是可能的情况。

③也即是说,r若影响结果只有一种可能,那就是r+r1=target,除此之外,r的覆盖与否不影响算法正确性。

④所以,遇到重复值的时候,需要判断,如果r1+r2<>target,那么可以覆盖也可以不覆盖,看你心情;如果r1+r2=target,那么直接返回map中r1的下标和当前的r2循环位置即可,over。

class Solution {
    public int[] twoSum(int[] numbers, int target) {

        HashMap<Integer,Integer> map = new HashMap<Integer,Integer>();
        for(int i=0;i<numbers.length;i++){
            if(map.containsKey(numbers[i])){
                if(numbers[i]*2==target){
                    return new int[]{map.get(numbers[i]),i};
                }
            }
            int tmp = target - numbers[i];
            if(map.containsKey(tmp)){
                return new int[]{i,map.get(tmp)};
            }else{
                map.put(numbers[i],i);
            }
        }
        return new int[]{};
    }
}
第三版:双指针法

在有序数组中,假设数组为 【2,4,7,9,11】

那么数组中两数之和设为·sum·,·sum>=6· 且·sum<=20·。这是根据2+4 与9+11的得出的结论。当然肯定是正确的。

当初始化指针·left·和·right·分别为数组边界的下标。

前提:·int tmp = (numbers[left] + numbers[right]);·

有如下选择:

  • ·(tmp^target)·==0 ====> 返回结果,不需要解释
  • ·(tmp<target)· ===>·left++· 【向着【9,11】组合走去】逐渐变大 迫近·target·
  • ·(tmp>target)· ===>·right–· 【向着【2,4】组合走去】逐渐变小 也是迫近·target·

这个选择就可以找出正确结果。

因为数组有序,所以·left·++一定比上一个结果大,·right·–一定比上一个结果小。反之,如果·(tmp<target) ·的时候,不动·left·而是去移动·right·,那么结果肯定越来小,因为·left·不变,·right·对应的值小,那么肯定更比不上·target·了。

代码很简单,这里就不加详细注释了🙏
image.png

class Solution {
    public int[] twoSum(int[] numbers, int target) {

        return first01(numbers,target);
    }

    public static int[] first01(int[] numbers,int target){

        int left=0;
        int right = numbers.length-1;

        while(left<right){
            int tmp = (numbers[left] + numbers[right]);
            if((tmp^target)==0){
                return new int[]{left,right};
            }else if(tmp<target){
                left++;
            }else{
                right--;
            }
        }
        return new int[]{0,0};
    }

}

大家好,我是二十一画,感谢您的品读,如有帮助,不胜荣幸~😊

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值