list 数组 查询 值相近的 坐标_数组相关算法题目中快慢双指针总结

c848eb9e5ad31ca209428f509a4ab191.png

快慢双指针经常用来解决数组在遍历的过程中时间复杂度是O(n)的情况。

a82674f2b7764844a8c79ec70d768e59.png
image.png

例如这个题目:

11. 盛最多水的容器

示例 1:

d32e2a9391368a15c16254c7efacc061.png
image
输入:[1,8,6,2,5,4,8,3,7]
输出:49 
解释:图中垂直线代表输入数组 [1,8,6,2,5,4,8,3,7]。在此情况下,容器能够容纳水(表示为蓝色部分)的最大值为 49。

示例 2:

输入:height = [1,1]
输出:1

示例 3:

输入:height = [4,3,2,1,4]
输出:16

示例 4:

输入:height = [1,2,1]
输出:2

来源:力扣(LeetCode)

链接:https://leetcode-cn.com/problems/container-with-most-water

著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。

根据生活常识,木桶能盛水的水量,取决于最短的一块板子,如下图所示:

4fe453770b1b6685e5892679b225b1d0.png
image.png

可以先给这个示例进行一个编号:

1-9号

d5cbb9d73f80dfb96997434c3224cea1.png
image.png

怎么才能找到盛水最多的容器呢?肯定是取决于板子的高低。

如果我们拿①号板子到⑨号板子来说的话:

①号板子的高度是1;

⑨号板子的高度是7;

那么这个范围的成水量就取决于①号板子的高度,因为在①和⑨号板子中,①号板子的高度是最低的。

如果我们拿②号板子到⑨号板子来说的话:

3fb67c7108d710aab089b9ea94208234.png
image.png

②号板子的高度是8;

⑨号板子的高度是7;

那么这个范围的成水量就取决于⑨号板子的高度,因为在②和⑨号板子中,⑨号板子的高度是最低的。

这个范围的盛水量是:7(高)* 7(宽)=49

这道算法题就是让我们找出可以盛水最多的容器,那么很明显,这个图中的最大值就是49。

在业界通常流传着一句话:往往第一次想到的解决办法都不是最好的,或者说都不是最优的。

能解决这道算法题有很多种办法,例如我们可以穷举每一块板子到其他所有板子的最大值。最终也可以拿到结果,无非就是复杂度高一点。

暴力解法

如下代码:

在下面这段暴力解法中,可以看到思路还是挺简单的,就是对每一根板子进行遍历,求出来每一个板子与其他所有板子的区域面积,最终选取最大的一个面积。

class Solution {
    public int maxArea(int[] height) {
        int max = 0;

        for(int i = 0;i
            //j = i+1 ;这里为什么要i+1呢?因为是不让重复的走自己走过的路
            for(int j = i+1 ; j 
                int high =  Math.min(height[i],height[j]);//选出最小的板子的高度

                int wide = j - i;//宽

                int area = high * wide;//容量面积 = 高 * 宽

                max = Math.max(area,max);//保留最大值的容量
            }

        }

        return max;
    }
}

但是我们可以看到,虽然能顺利的解出这道题,但是时空复杂度是不这么完美的。如果你是一个追求完美的人,可能就需要进行一番折腾去想办法去优化了。

9525cdeb3635e376c08ffac2e1b3230b.png
image.png
双指针

代码如下:

我们定义两个指针,一个指向数组的头,一个指向数组的尾。

然后  头---> 逐渐逼近

class Solution {
    public int maxArea(int[] height) {
        //定义双指针
        int i = 0;//指针一,指向头
        int j = height.length - 1;//指针二,指向尾
        int max = 0;//记录最终最大的值

        while(j -i >=1){

            max = Math.max(Math.min(height[i],height[j]) * (j-i),max);

            //移动缩小范围
            if(height[i] <= height[j] ){
                i++;//指针一向后移动
            }else{
                j--;//指针二向前移动
            }    
        }
        return max;
    }
}

经过我们的优化,可以看到已经有效果了

d1e8b6fcb47f7b7b40c8eeb6ffc7e9f4.png
image.png

1. 两数之和

为了更详细的说明接下来的问题,先拿出两数之和这个题目来说一下。

题目如下:

//给定一个整数数组 nums 和一个目标值 target,请你在该数组中找出和为目标值的那 两个 整数,并返回他们的数组下标。 
//
// 你可以假设每种输入只会对应一个答案。但是,数组中同一个元素不能使用两遍。 
//
// 
//
// 示例: 
//
// 给定 nums = [2, 7, 11, 15], target = 9
//
//因为 nums[0] + nums[1] = 2 + 7 = 9
//所以返回 [0, 1]
// 
// Related Topics 数组 哈希表 
// ? 9725 ? 0

链接:https://leetcode-cn.com/problems/two-sum

这个题我们可以使用暴力手段:

思路还是很明确的,当nums[i] + nums[j] = target时,即可返回i和j坐标

    public int[] twoSum(int[] nums, int target) {
        for (int i = 0; i 1; i++) {
            for (int j = i+1; j                 if(nums[i] + nums[j] == target){
                    return new int[]{i,j};
                }
            }
        }

        return new int[]{};
    }

可以看到虽然是暴力的手段,单纯的看着效果还是挺不错的。

8195250829f7cdd78d5cab8fadbcaabb.png
image.png

那么有没有优化的可能性呢,答案是有的,例如:可以使用hash表来存储。

    public int[] twoSum(int[] nums, int target) {
        int[] result = new int[2];
        Map map = new HashMap();for (int i = 0; i             if (map.containsKey(target - nums[i])) {
                result[1] = i;
                result[0] = map.get(target - nums[i]);return result;
            }
            map.put(nums[i], i);
        }return result;
    }
c825320888811530c7ae67e217fbe89e.png
image.png

15. 三数之和

题目:

//给你一个包含 n 个整数的数组 nums,判断 nums 中是否存在三个元素 a,b,c ,使得 a + b + c = 0 ?请你找出所有满足条件且不重复
//的三元组。 
//
// 注意:答案中不可以包含重复的三元组。 
//
// 
//
// 示例: 
//
// 给定数组 nums = [-1, 0, 1, 2, -1, -4],
//
//满足要求的三元组集合为:
//[
//  [-1, 0, 1],
//  [-1, -1, 2]
//]
// 
// Related Topics 数组 双指针 
// ? 2785 ? 0

力扣链接:https://leetcode-cn.com/problems/3sum

暴力解法

对于二数之和的题目,我们可以

当nums[i] + nums[j] = target时,即可返回i和j坐标

那么此题中三数之和,我们肯定也可以类似二数之和的做法一样会去做:

1、三重for循环遍历nums数组;

2、找到nums[i]+nums[j]+nums[k] = 0 保存nums[i],nums[j],nums[k];

3、继续寻找;

4、返回保存的数据即可。

那么上面这个思路就是暴力的手段了。

 public List> threeSum(int[] nums) {
        List> result = new ArrayList<>();int len = nums.length;//利用set去重
        Set> set = new HashSet<>();for (int i = 0; i             for (int j = i+1; j                 for (int k = j+1; k if(nums[i]+nums[j]+nums[k] == 0){
                        List list = new ArrayList<>();
                        list.add(nums[i]);
                        list.add(nums[j]);
                        list.add(nums[k]);
                        Collections.sort(list);
                        set.add(list);
                    }
                }
            }
        }return new ArrayList<>(set);
    }

很遗憾的是,暴力解法跑不过测试程序,超时了:

c1e89ea167de4d7493180011edc5a19b.png
image.png
双指针解法

双指针可以解决数组相关算法题的大多数超时问题。

 public List> threeSum(int[] nums) {
        List> result = new ArrayList<>();//如果传递的数组是空的,直接返回一个空的list集合if(nums.length == 0) return result;//排序
        Arrays.sort(nums);int len = nums.length;//遍历for (int i = 0; i //跳过重复的值if(i>0 && nums[i] == nums[i-1])continue;/**
             * a=i
             * b=j
             * c=k
             *
             * 因为:a+b+c=0
             * 那么:b+c=-a
             * 那么nums[j] + nums[k] = -nums[i]
             *
             */int target = -nums[i];//目标数int j =  i +1;//头指针int k = len - 1;//尾指针//两个指针到中间相遇了,既结束while ( j //判断 b+c = -a 是否成立if(nums[j] + nums[k] == target){//组装数据
                    List curr = new ArrayList<>();
                    curr.add(nums[i]);
                    curr.add(nums[j]);
                    curr.add(nums[k]);//添加到结果集
                    result.add(curr);//移动头指针和尾指针,缩小范围
                    j++;
                    k--;//进一步减小范围//1、检查左边while (j 1]) j++;//2、检查右边while (k > j && nums[k] == nums[k + 1]) k--;//如果符合b+c > -a/*
                    例如:[-4,-1,-1,0,1,2]
                    a=-4
                    b=-1
                    c=2
                     i  j         k
                     ↓  ↓         ↓
                     a  b         c
                     ↓  ↓         ↓
max data index(j) → [-4,-1,-1,0,1,2] ← min data index(k)
                    b+c=-1 != --4(4)
                    说明b+c太大了,所以我们要让b+c变小,那么就必须缩小大头
                    因为k指针是指向的数组中最大的一个范围,那么就需要缩小k所指的位置
                    则:k--
                 */
                }else if(nums[j] + nums[k] > target){
                    k--;
                }else{//反之,j++;增大数据
                    j++;
                }
            }
        }return result;
    }

代码虽然比之前复杂了点,但是效果是很好的:

94469df6e7e6205c3f47488538e6ecae.png
image.png
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值