目录
理论基础
1. 对撞指针
- 指针从数组或序列的两端向中间移动。
- 一般用于涉及数组中的元素两两配对,求和、判断条件或寻找某些特定的组合。
- 典型问题:
- 两数之和 II(有序数组):在有序数组中找到两个数,使它们的和为给定值。
- 验证回文字符串:通过前后指针向中间移动,比较字符来判断是否为回文。
2. 快慢指针
- 一个指针移动得快(通常一次两步),另一个指针移动得慢(通常一次一步)。
- 用于检测链表中的循环、寻找链表的中间节点等。
- 典型问题:
- 判断链表是否有环:通过快慢指针,快指针比慢指针快两倍,若链表有环,两者最终会相遇。
- 寻找链表的中间节点:当快指针到达末尾时,慢指针正好处于链表中间。
3. 左右边界法
- 双指针分别从左右两端开始,根据某种条件调整左指针或右指针的位置,通常是为了找极值。
- 适用于需要动态更新区间边界的问题,常用于解决动态最大值或最小值问题。
- 典型问题:
- 接雨水问题:用双指针分别从数组的两端向中间移动,根据较小的边界来决定能容纳多少雨水。
- 盛最多水的容器:通过调整左、右指针,找到能够容纳最多水的两个柱子。
4. 滑动窗口
- 双指针同时从一端出发,一个指针作为窗口的左边界,另一个指针作为右边界,动态扩大或缩小窗口。
- 适用于求子数组或子串的最大值、最小值或满足某些条件的长度等。
- 典型问题:
- 最长无重复子串:通过滑动窗口动态维护一个无重复字符的子串,并记录其最大长度。
- 最小覆盖子串:找到包含给定字符串中所有字符的最小子串。
5. 归并场景
- 指针分别指向两个已经排序的数组,通过移动指针来合并两个有序数组或链表。
- 常用于排序或者合并有序序列的问题。
- 典型问题:
- 合并两个有序数组:利用双指针将两个有序数组归并为一个新的有序数组。
双指针法的优势在效率上:通过两个指针在一个for循环下完成两个for循环的工作
可用于哈希、数组、数组、链表等多种结构
*官方归纳的双指针篇基本都是对撞指针和左右边界指针
283.移动零
给定一个数组 nums,编写一个函数将所有 0 移动到数组的末尾,同时保持非零元素的相对顺序。
请注意 ,必须在不复制数组的情况下原地对数组进行操作。
示例 1:
输入: nums = [0,1,0,3,12]
输出: [1,3,12,0,0]
示例 2:
输入: nums = [0]
输出: [0]
提示:
1 <= nums.length <= 104
-231 <= nums[i] <= 231 - 1
进阶:你能尽量减少完成的操作次数吗?
class Solution {
public void moveZeroes(int[] n) {
if(n==null) return;
int i,j=0;
for(i=0;i<n.length;i++){//第三部分++i;i++没影响
if(n[i]!=0) n[j++]=n[i];
}
for(i=j;i<n.length;++i){
n[i]=0;
}
}
}
11.盛水最多
给定一个长度为 n 的整数数组 height 。有 n 条垂线,第 i 条线的两个端点是 (i, 0) 和 (i, height[i]) 。
找出其中的两条线,使得它们与 x 轴共同构成的容器可以容纳最多的水。
返回容器可以储存的最大水量。
说明:你不能倾斜容器。
输入:[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
提示:
n == height.length
2 <= n <= 105
0 <= height[i] <= 104
可证明:将高度小的指针往内移动,才会枚举到更大的面积
(移动较短边是为了找到更高的边,增大容量,而移动较长边只会减少宽度,无法增加容量,所以不会漏掉最大值)
//简化版本
class Solution {
public int maxArea(int[] h) {
int i=0,j=h.length-1,r=0;
while(i<j){
r=h[i]<h[j] ? Math.max(r,(j-i)*h[i++]):Math.max(r,(j-i)*h[j--]);
}
return r;
}
}
class Solution {
public int maxArea(int[] h) {
int i=0,j=h.length-1,r=0;
while(i<j){
int a;
if(h[i]<h[j]) a=(j-i)*h[i++];
else a=(j-i)*h[j--];
r=Math.max(r,a);
}
return r;
}
}
15.三数之和
给你一个整数数组 nums ,判断是否存在三元组 [nums[i], nums[j], nums[k]] 满足 i != j、i != k 且 j != k ,同时还满足 nums[i] + nums[j] + nums[k] == 0 。请你返回所有和为 0 且不重复的三元组。
注意:答案中不可以包含重复的三元组。
示例 1:
输入:nums = [-1,0,1,2,-1,-4]
输出:[[-1,-1,2],[-1,0,1]]
解释:
nums[0] + nums[1] + nums[2] = (-1) + 0 + 1 = 0 。
nums[1] + nums[2] + nums[4] = 0 + 1 + (-1) = 0 。
nums[0] + nums[3] + nums[4] = (-1) + 2 + (-1) = 0 。
不同的三元组是 [-1,0,1] 和 [-1,-1,2] 。
注意,输出的顺序和三元组的顺序并不重要。
示例 2:
输入:nums = [0,1,1]
输出:[]
解释:唯一可能的三元组和不为 0 。
示例 3:
输入:nums = [0,0,0]
输出:[[0,0,0]]
解释:唯一可能的三元组和为 0 。
提示:
3 <= nums.length <= 3000
-105 <= nums[i] <= 105
class Solution {
public static List<List<Integer>> threeSum(int[] n) {
List<List<Integer>> r = new ArrayList<>();
int l = n.length;
if (n == null || l < 3) return r;
Arrays.sort(n);
for (int i = 0; i < l; i++) {
if (n[i] > 0) break;//剪枝
if (i > 0 && n[i] == n[i - 1]) continue;// 去重
int L = i + 1, R = l - 1;
while (L < R) {
int sum = n[i] + n[L] + n[R];
if (sum == 0) {
r.add(Arrays.asList(n[i], n[L], n[R]));//例如[[-1, -1, 2], [-1, 0, 1]]
// 去重,并移动左右指针
while (L < R && n[L] == n[L + 1]) L++;
while (L < R && n[R] == n[R - 1]) R--;
L++;
R--;
} else if (sum < 0) L++;
else R--;
}
}
return r;
}
}
42.接雨水h
给定 n 个非负整数表示每个宽度为 1 的柱子的高度图,计算按此排列的柱子,下雨之后能接多少雨水。
示例 1:
输入:height = [0,1,0,2,1,0,1,3,2,1,2,1]
输出:6
解释:上面是由数组 [0,1,0,2,1,0,1,3,2,1,2,1] 表示的高度图,在这种情况下,可以接 6 个单位的雨水(蓝色部分表示雨水)。
示例 2:
输入:height = [4,2,0,3,2,5]
输出:9
提示:
n == height.length
1 <= n <= 2 * 104
0 <= height[i] <= 105
class Solution {
public int trap(int[] h) {
int r=0,L=0,R=h.length-1,pm=0,am=0,tmp;
//pm左侧的最高柱子,am右侧的最高柱子
while (L < R) {
pm=Math.max(pm, h[L]);
am = Math.max(am, h[R]);
tmp = pm < am ? pm - h[L++] : am - h[R--]; //计算左右柱子能接的水量,左右两侧最高中更小决定当前的水量
r=r+tmp;
}
return r;
}
}
单调栈法
class Solution {
public int trap(int[] h) {
int r = 0;
Deque<Integer> d = new ArrayDeque<>(); // 用栈存放柱子的索引
for (int i = 0; i < h.length; i++) {
while (!d.isEmpty() && h[i] >= h[d.peek()]) {
int j= h[d.pop()]; // 栈顶元素表示低洼处的柱子高度
if (d.isEmpty()) break;
int left = d.peek();
int dh = Math.min(h[left], h[i]) - j; // 计算高度差
r += dh * (i - left - 1); // 计算接雨水的面积并累加
}
d.push(i); // 将当前柱子的索引入栈
}
return r;
}
}