快慢双指针经常用来解决数组在遍历的过程中时间复杂度是O(n)的情况。
![a82674f2b7764844a8c79ec70d768e59.png](https://i-blog.csdnimg.cn/blog_migrate/0e1d9064b99e9649a755b7505d38f90a.png)
例如这个题目:
11. 盛最多水的容器
示例 1:
![d32e2a9391368a15c16254c7efacc061.png](https://i-blog.csdnimg.cn/blog_migrate/24c000a5bb6db154c4a6c00c63b49297.jpeg)
输入:[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](https://i-blog.csdnimg.cn/blog_migrate/a3660e4ad38a549f9e0f42b978556f25.png)
可以先给这个示例进行一个编号:
1-9号
![d5cbb9d73f80dfb96997434c3224cea1.png](https://i-blog.csdnimg.cn/blog_migrate/5504735de1cef659e8c6c1eb6374592b.png)
怎么才能找到盛水最多的容器呢?肯定是取决于板子的高低。
如果我们拿①号板子到⑨号板子来说的话:
①号板子的高度是1;
⑨号板子的高度是7;
那么这个范围的成水量就取决于①号板子的高度,因为在①和⑨号板子中,①号板子的高度是最低的。
如果我们拿②号板子到⑨号板子来说的话:
![3fb67c7108d710aab089b9ea94208234.png](https://i-blog.csdnimg.cn/blog_migrate/ac4d41dc5ad9d721fe2ad88599021113.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](https://i-blog.csdnimg.cn/blog_migrate/fd3641a7fe7a085fa3554725dc7a3744.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](https://i-blog.csdnimg.cn/blog_migrate/9b814d8b62500b4f834e338e8ad2584a.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](https://i-blog.csdnimg.cn/blog_migrate/7b9b84e1fbc80280805f75d93bae031c.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](https://i-blog.csdnimg.cn/blog_migrate/207b4e403a453c0c3549a2553c5be660.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](https://i-blog.csdnimg.cn/blog_migrate/da9761dcca5a85c8c316476040b67b70.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](https://i-blog.csdnimg.cn/blog_migrate/b3606a660d5cc558f68be9ebdd0630f7.png)