这是我参与更文挑战的第7天,活动详情查看:更文挑战java
引言:什么是双指针呢?故名思义,就是两个指针。这么说可能有点欠揍,经过问题来讲明应该更直观。众所周知,链表有一种数据结构为环形链表,即链表中有某个节点,能够经过连续跟踪 next
指针再次到达,则链表中存在环。那么给你一个链表,你怎么判断其中有环呢?其中一种思路是,选定两个指针,同时从好比 a
节点出发,一个指针一次走一步,a.nex
,一个指针一次走两步,a.next.next
,若是它俩某一次又重合到了同一节点,就说明链表中是有环的。这样对于双指针你就应该有了更直观的认识。git
上面提到的双指针,又被称做“快慢指针”,另外,双指针还有“对撞指针”,即从链表或者数组两端往中间走。包括“滑动窗口”也是双指针的思想。今天咱们就来看看“双指针”的应用。github
就从引言中的问题来看,如何判断一个列表是环形链表呢?首先给出列表节点的数据结构:算法
public class ListNode {
int val;
ListNode next;
ListNode(int x) {
val = x;
next = null;
}
}
复制代码
节点的数据结构包含本节点的值,以及 next
节点。按照快慢指针的思路,咱们就能够写出下面的判断代码:数组
/** * 判断链表是否有环。使用快慢指针便可解决 * * @param head 链表 * @return 是否有环 */
public boolean hasCycle(ListNode head) {
// 快慢指针都是从头部节点出发
ListNode fast = head;
ListNode low = head;
while (fast != null && fast.next != null) {
// 快指针一次走两步
fast = fast.next.next;
// 慢节点一次走一步
low = low.next;
// 当两个指针走到同一位置时,代表链表中存在环形结构,即环形链表
if (fast == low) {
return true;
}
}
return false;
}
复制代码
所谓对撞指针,就是说咱们能够将指向最左侧的索引定义为左指针(left)
,最右侧的定义为右指针(right)
,两个指针从两端往中间靠拢,最终到达相同位置,即撞在一块儿。这种方法通常适用于有序列表或者数组,能够更好地肯定位置。二分查找也是能够用这种思路来解决的,首先肯定中间位置的值,若是比目标值小,则左指针从中间位置向右移,不然,右指针从中间位置向左移。能够简单看下代码:markdown
/** * 二分查找非递归写法 * * @param nums 数组 * @param target 目标值 * @param left 左指针 * @param right 右指针 * @return 目标值位置 */
public static int binarySearch(int[] nums, int target, int left, int right) {
// 这里须要注意,循环条件
while (left <= right) {
// 这里须要注意,计算mid
int mid = left + ((right - left) >> 1);
if (nums[mid] == target) {
return mid;
} else if (nums[mid] < target) {
// 左指针向右移动
left = mid + 1;
} else if (nums[mid] > target) {
// 右指针向左移动
right = mid - 1;
}
}
// 没有找到该元素,返回 -1
return -1;
}
复制代码
LeetCode 上有一个比较典型的问题,救生艇问题:数据结构
#881. 救生艇
第 i 我的的体重为 people[i],每艘船能够承载的最大重量为 limit。
每艘船最多可同时载两人,但条件是这些人的重量之和最多为 limit。
返回载到每个人所需的最小船数。(保证每一个人都能被船载)。
示例 1:
输入:people = [1,2], limit = 3
输出:1
解释:1 艘船载 (1, 2)
示例 2:
输入:people = [3,2,2,1], limit = 3
输出:3
解释:3 艘船分别载 (1, 2), (2) 和 (3)
示例 3:
输入:people = [3,5,3,4], limit = 5
输出:4
解释:4 艘船分别载 (3), (3), (4), (5)
复制代码
这时候咱们就能够有这么一种思路,既然是求所需的救生艇的最小数量,那么就须要每条船上承载更多重量(数量上已经限定了最多两人)。因此,首先是对重量进行排序,若是最重的人能够与最轻的人共用一艘船,那么就这样安排;不然,最重的人没法与任何人配对,那么他们将本身独自乘一艘船。而后,最轻的人继续检查是否和次重的共用一条船,若是能够,第二轻的人继续和第三重的人进行检查是否能够共用,以此类推……代码以下:oop
/** * 思路:双指针 * 若是最重的人能够与最轻的人共用一艘船,那么就这样安排。不然,最重的人没法与任何人配对,那么他们将本身独自乘一艘船。 * * @param people * @param limit * @return 所需最小船数量 */
public int solution(int[] people, int limit) {
// 首先进行排序
Arrays.sort(people);
// 左右指针
int i = 0, j = people.length - 1;
// 所需船数量
int ans = 0;
while (i <= j) {
ans++;
if (people[i] + people[j] <= limit) {
// 若是两我的能够共用一条船,左指针向右移
i++;
}
// 右指针向左移,可能存在能够共用救生艇的人,也可能比较重的人独自乘一艘船
j--;
}
return ans;
}
复制代码
这个名称可能你们就比较熟悉了,就是说有两个指针,一前一后组成滑动窗口,窗口的宽度能够是固定的,算法题相关的话大部分都是不固定宽度的,而后计算滑动窗口中元素的值。post
咱们能够经过下面这道题更为直观的了解。spa
#209. 长度最小的子数组
给定一个含有 n 个正整数的数组和一个正整数 target 。
找出该数组中知足其和 ≥ target 的长度最小的 连续子数组 [numsl, numsl+1, ..., numsr-1, numsr] ,并返回其长度。若是不存在符合条件的子数组,返回 0 。
示例 1:
输入:target = 7, nums = [2,3,1,2,4,3]
输出:2
解释:子数组 [4,3] 是该条件下的长度最小的子数组。
示例 2:
输入:target = 4, nums = [1,4,4]
输出:1
示例 3:
输入:target = 11, nums = [1,1,1,1,1,1,1,1]
输出:0
提示:
1 <= target <= 109
1 <= nums.length <= 105
1 <= nums[i] <= 105
复制代码
下面咱们来看一下这道题目的作题思路,其实原理也很简单,咱们建立两个指针,一个指针负责在前面探路,并不断累加遍历过的元素的值,当和大于等于咱们的目标值时,后指针开始进行移动,判断去除当前值时,是否仍能知足咱们的要求,直到不知足时后指针中止,前面指针继续移动,直到遍历结束。前指针和后指针之间的元素个数就是咱们的滑动窗口的窗口大小,即最小子数组长度。
/** * 滑动窗口:就是经过不断调节子数组的起始位置和终止位置,进而获得咱们想要的结果,滑动窗口也是双指针的一种。 * * @param target 目标值 * @param nums 数组 * @return len 最小长度 */
public int minSubArrayLen(int target, int[] nums) {
int len = nums.length;
int windowLen = Integer.MAX_VALUE;
// i 后指针
int i = 0;
int sum = 0;
for (int j = 0; j < len; ++j) {
// j 为前指针
sum += nums[j];
while (sum >= target) {
// 比较求最小值
windowLen = Math.min(windowLen, j - i + 1);
// 移除当前值
sum -= nums[i];
// 移动后指针
i++;
}
}
return windowLen == Integer.MAX_VALUE ? 0 : windowLen;
}
复制代码
对于查找某个重复值,判断是否有环,两个链表是否有相交节点等等这类的题目,咱们均可以首先使用 快慢指针 来解决;对于有序的列表或者数组,咱们能够优先考虑一下 对撞指针 来解决;滑动窗口 则很适用于求数组某个范围内元素的计算结果。