【双指针】N数之和

两数之和

题目

题目链接: 查找总价格为目标值的两个商品

虽然题目名字不是两数之和, 但是由于和后面的三数之和, 四数之和是连起来的, 于是换一个名字.

正统的两数之和是这个连接: 两数之和, 它的区别是返回的是下标, 而不是数字本身

题目解析

非常容易理解, 就是返回两个数字, 这两个数字的和要是 target

暴力思路

暴力思路也就是经典的枚举出所有的二元组, 依次查看和是否为 target 即可

使用一个简单的双重for循环就可以达到这个效果

class Solution {
    public int[] twoSum(int[] price, int target) {
        for(int i = 0; i < price.length; i++){
            for(int j = i + 1; j < price.length; j++){
                if(price[i] + price[j] == target){
                    return new int[]{price[i] ,price[j]};
                }
            }
        }
        return null;
    }
}

但是由于这个题目的数组长度最大是10^5, 因此时间复杂度为O(n^2)的算法是一定会超时的

双指针优化

实际上这一题有一个条件在我们使用暴力解法的时候没有用到, 就是按照升序记录. 也就是说这个数组是已经排好序了, 这是一个非常重要的提示.

此时我们就要想到题目的要求, 实际上是求a + b = target. 那么实际上在暴力枚举的过程中, 就只有三种情况会发生, 分别是a + b > target, a + b < target, a + b = target

那么此时就有一个双指针思路非常适合去一个有序的数组中寻找a + b = target这种情况, 具体步骤如下:

首先先让 left 和 right 指向左侧和右侧, 然后对下列情形分别进行不同操作, 直到left > right

  1. left + right > target, 此时right–
  2. left + right < target, 此时left++
  3. left + right = target, 此时找到答案

此时的这个指针由于两者向内, 因此也有一个特殊的名称叫做对撞指针

那么为什么可以这样操作呢? 实际上这是也是由于有序数组的单调性决定的. 我们使用上面题目的例子来看

在这里插入图片描述

可以看到, 我们要找61, 刚开始left = 8, right = 66.

此时8 + 66 > 61, 很明显此时计算的结果是更大的, 那么此时我们如果要找到61这个值, 就需要让我们的left + right的值变小. 由于数组是有序的, 如果我们想要让left + right 变小, 就只能让 right 往左走了.

下一步到达了8 + 52 < 61, 此时同上, 结果太小, 需要让结果变大, 那么此时就只能让left往右走了.

随后一直循环进行判断, 就会走到left = 27 right = 34 的情况

思路还是较为简单的, 下面就是代码

class Solution {
    public int[] twoSum(int[] price, int target) {
        // 指向左侧和右侧
        int left = 0, right = price.length - 1;

        while(left <= right){
            // 分别对应三种情况
            if(price[left] + price[right] > target) right--;
            else if(price[left] + price[right] < target) left++;
            else return new int[]{price[left], price[right]};
        }

        return null;
    }
}

三数之和

题目

题目链接: 三数之和

题目解析

题目本身描述的就是找三个数令其和为0, 同时这三个数字不能是同一个. 我们同样可以通过例子来理解

首先看第一个例子, 可以看出实际上就是要你从数组中找三个的数字, 让其和为0. 这三个数字的顺序不重要

在这里插入图片描述

同时我们可以从第二个例子中看出, 他要求的是不同的数, 因为他选不了0 0 0

在这里插入图片描述

暴力思路

这里的暴力思路就和两数之和的一样, 遍历所有三元组. 就是一个三重for循环, 时间复杂度为O(n^3), 这里我们就不书写了

双指针优化

对于这一题, 实际上核心点在于两个: 1. 找三个数 2. 去重

对于第一个点, 我们可以化繁为简. 我们这一题的要求是求出三个数, 令其结果是0. 那么我们是否可以换一种思路, 我们找三个数a b c, 能否去找b + c = -a呢? 答案是当然可以的, 同时求这个b + c = -a的过程, 我们就可以参考两数之和的求解过程. 这种化繁为简, 将难题化为简单的题的思路还是比较常用的.

那么参考两数之和的求解, 首先我们就需要对数组进行排序, 然后从第一个数开始, 把每一个数当为a, 然后在其后方区域进行两数之和的操作. 下面是一个例子

在这里插入图片描述

解决了第一个点, 接下来就是第二个点, 去重. 这里有一个非常简单的方法就是使用自带的集合类去去重, 比如Java中的HashSet. 但是我们这里就不使用这种方法了, 而是使用手动的方式来实现.

上面我们说过, 我们是需要对数组进行排序的, 那么相对应的就是, 相同的数字会在同一个部分, 如下所示

在这里插入图片描述

那么此时我们就可以在过程中, 当当前数字和上一个数字不同的时候, 存储数字, 如果当前数字和上一个数字相同, 那么就跳过这些相同的数字. 但是这里要注意的是, 无论是选择a, 或者是b + c的过程, 都需要跳过相同的数字

这题的思路相较于上面的两数之和还是较为复杂的, 不过根据我们上面的分析, 大致可以分为如下几个步骤:

  1. 对数据进行排序
  2. 使用循环从第一个数字开始往后逐个作为数字 a, 同时需要进行去重
  3. 对 a 后方的数字进行寻找b c 的操作, 使用两数之和的方式寻找, 同时需要进行去重

下面是代码

class Solution {
    public List<List<Integer>> threeSum(int[] nums) {
        // 对数组进行排序
        Arrays.sort(nums);
        // 创建返回值
        List<List<Integer>> ret = new ArrayList<>();

        int n = nums.length;
        // 从第一个数字开始往后循环
        for(int a = 0; a < n - 2;){
            // 创建left和right用于执行两数之和
            int left = a + 1;
            int right = n - 1;
            int target = -nums[a];

            // 执行两数之和
            while(left < right){
                int sum = nums[left] + nums[right];
                if(sum > target) right--;
                else if(sum < target) left++;
                else {
                    // 此时找到了一个元组, 放入nums
                    ret.add(Arrays.asList(nums[a], nums[left], nums[right]));
                    // 两侧指针向内移动
                    left++; 
                    right--;

                    // 去重
                    while(left < right && nums[left] == nums[left - 1]) left++;
                    while(left < right && nums[right] == nums[right + 1]) right--;
                } 
            }
            
            // 对a去重
            a++;
            while(a < n - 2 && nums[a] == nums[a - 1]) a++;
        }

        return ret;
    }
}

四数之和

题目

题目链接: 四数之和

题目解析

经历了上面两道题, 这道题也十分好理解, 就是求不同的四个数使其总和为 target

暴力思路

暴力思路也和上面的一样, 经典嵌套for循环, 这里是求4个数那么自然就是四重for循环, 时间复杂度为O(n^4), 这里不再阐述

双指针优化

双指针优化和三数之和的思路一致, 先找一个数, 在后方区间求三数之和, 然后三数之和的思路就和前面一样, 那么总结下来的流程如下

  1. 先排序数组
  2. 使用一个数 a 遍历数组, 设 a 后方区间为[m, n], 那么就是在[m, n]区间内使用三数之和思路寻找 target - nums[a]
  3. 同三数之和思路, 在[m, n]区间内使用一个 b 遍历区间, 同时在 b 后方使用两数之和思路寻找 target - nums[a] - nums[b]
  4. 两数之和思路不在阐述
  5. 中间过程均需要去重

最终代码如下

class Solution {
    public List<List<Integer>> fourSum(int[] nums, int target) {
        // 排序
        Arrays.sort(nums);

        List<List<Integer>> ret = new ArrayList<>();
        int n = nums.length;
        // 四数之和
        for(int i = 0; i < n;){
            
            // 三数之和
            for(int j = i + 1; j < n;){
                
                // 两数之和
                int left = j + 1;
                int right = n - 1;
                // 这里使用long是由于恶心人的测试用例
                long targetTwo = (long)target - nums[i] - nums[j];

                while(left < right){
                    int sum = nums[left] + nums[right];
                    if(sum > targetTwo) right--;
                    else if(sum < targetTwo) left++;
                    else{
                        // 找到元组, 放入并收缩
                        ret.add(Arrays.asList(nums[i], nums[j], nums[left], nums[right]));
                        left++;
                        right--;
                        // 去重
                        while(left < right && nums[right] == nums[right + 1]) right--;
                        while(left < right && nums[left] == nums[left - 1]) left++;
                    }
                }

                // 去重
                j++;
                while(j < n && nums[j] == nums[j - 1]) j++;

            }

            // 去重
            i++;
            while(i < n && nums[i] == nums[i - 1]) i++;
        }

        return ret;
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值