双指针解决两数之和的问题

很多事情大家都心知肚明,只是聪明人不说出来


关于两数之和的问题,一般都是给定数组nums目标值target, 处理这种问题一般需要注意以下两点:

  1. 数组是否有序;

  2. 给定目标值target,根据两数之和与target的大小情况展开讨论:
    - 等于target
    - 小于target
    - 大于target

  3. 返回值是下标 or 方案数

    如果需要返回值是下标,则在数组无序的情况下,我们是不能通过排序双指针方案来解决的。

结合Leetcode的题目,我们对上面所列举的情况逐一来展开讨论。

第一类:等于target

1. 两数之和

给定一个整数数组 nums 和一个整数目标值 target,请你在该数组中找出 和为目标值 target 的那 两个 整数,并返回它们的数组下标

根据上面的总结我们可以找出关键点:

  1. 数组无序;
  2. 两数之和等于target的情况;
  3. 返回数组下标;

所以此题就排除了排序 + 双指针的解决方案。推荐使用哈希表将时间复杂度给降到常数级。

解题思路就是遍历数组,对于每一个元素num,查询是否存在对应的 target - num。

	public int[] twoSum(int[] nums, int target) {
        int[] result = new int[2];

        if(nums == null || nums.length == 0){
            return result;
        }
        // 注意key为nums[i],value为下标
        HashMap<Integer,Integer> hashMap = new HashMap<>();

        for(int i = 0;i < nums.length; i++){

            if(hashMap.containsKey(target-nums[i])){
                result[0] = i;
                result[1] = hashMap.get(target-nums[i]);
                return result;
            }

            hashMap.put(nums[i],i);
        }
        return result;
    }

PS : 也可以用哈希表存储target-nums[i]实现。原理是一样的。

1.2 常见问题

很多同学对这里的哈希表究竟谁为key,谁为value有所疑问?相信自己,不是只有你一个人有此困惑

这里郑重声明:

  • key ——— 元素num[i] 或者 target-nums[i]
  • value ———— 元素下标

很多之所以困惑是 考虑到数组中存在重复元素时,其作为key会被覆盖的原因。
对于这个题目,题目中明确了只会存在一个有效答案。这样的话,就算有重复元素,找到一组解就直接返回啦。

那么问题来了,假设可能存在多组解的情况,这种解法还有效吗?

答曰:然也

举个例子:

输入用例为[1,1,2,5], target = 3

当遍历到第二个1的时候,hashMap会覆盖前面的1,然后继续执行。最终会返回[2,1]这组有效答案。

彩
纸上得来终觉浅,绝知此事要躬行

刷算法题时,很多时候不是我们没有思路,而是没有真正动起手去实现罢了。


167. 两数之和 II - 输入有序数组

给你一个下标从 1 开始的整数数组 numbers ,该数组已按 非递减顺序排列 ,请你从数组中找出满足相加之和等于目标数 target 的两个数。如果设这两个数分别是 numbers[index1] 和 numbers[index2] ,则 1 <= index1 < index2 <= numbers.length 。
以长度为 2 的整数数组 [index1, index2] 的形式返回这两个整数的下标 index1 和 index2。

关键点:

  1. 数组有序——非递减顺序排列;
  2. 找出满足相加之和等于目标数 target 的两个数;
  3. 返回下标且index1 < index2;
  4. 下标从1开始;

数组有序的情况下,双指针,舍我其谁?

如图所示

 public int[] twoSum(int[] numbers, int target) {
        int[] result = new int[2];
        if(numbers == null || numbers.length == 0){
            return result;
        }

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

        while(left < right){
           int sum =  numbers[left]+numbers[right];
            if( sum == target){
            	// 因为下标从1开始,
                result[0] = left+1;
                result[1] = right+1;
                return result;
            }

            if(sum < target){
                left++;
            }else{
                right--;
            }
        }

        return result;
    }

顺手补一刀吧—— 剑指 Offer 57. 和为s的两个数字


第二类:小于target

1099. 小于 K 的最大两数之和

给你一个整数数组 nums 和整数 k ,返回最大和 sum ,满足存在 i < j 使得 nums[i] + nums[j] = sum 且 sum < k 。如果没有满足此等式的 i,j 存在,则返回 -1 。

关键点:

  1. 数组无序
  2. (sum = nums[i] + nums[j]) < k;
  3. 返回 最大和sum尽可能的大;

不用想了,排序 + 双指针

 public int twoSumLessThanK(int[] nums, int k) {
        if(nums == null || nums.length == 0){
            return -1;
        }

        Arrays.sort(nums);

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

        int result = -1;

        while(left < right){
            int sum = nums[left] + nums[right];
            if(sum >= k){
                right--;
            }else{
            	// 求最大和
                result = Math.max(result,sum);
                left++;
            }
        }

        return result;
    }

LCP 28. 采购方案

小力将 N 个零件的报价存于数组 nums。小力预算为 target,假定小力仅购买两个零件,要求购买零件的花费不超过预算,请问他有多少种采购方案。

注意:答案需要以 1e9 + 7 (1000000007) 为底取模,如:计算初始结果为:1000000008,请返回 1

总结出关键点:

  1. 数组无序;
  2. num[i] + num[j] <= target;
  3. 求符合条件的方案数

这道题是竞赛题,我们来看一下同学们的常见问题:
在这里插入图片描述
对于我竟无言以对,唯有张飞三连送于大家。
在这里插入图片描述

解决此题有个核心要素:方案数的锁定(右边界 - 左边界)
在这里插入图片描述

public int purchasePlans(int[] nums, int target) {
        if(nums == null || nums.length == 0){
            return 0;
        }
		// 排序
        Arrays.sort(nums);

        int result = 0;

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

        while(left <= right){
            int sum = nums[left] + nums[right];
            if(sum > target){
                right --;
            }else{
            	// 这里利用[left,right]的元素都符合sum <= target的特性,直接得出方案数为right - left.
                result += (right - left);
                result %= (1000000007);
                left++;
            }
        }
        return result;
    }
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值