在人生中第一要紧的是发现自己。为了这个目的,各位时常需要孤独和深思 —— 南森
应用场景
双指针的思想主要应用在链表、数组、字符串等这些数据结构上面,下面我们就重点讲解了双指针的方式在链表中的应用,而且这几道题在面试中也是经常出现的简单题目
例题
1. 链表中倒数第k个节点
题目
输入一个链表,输出该链表中倒数第k个节点。为了符合大多数人的习惯,本题从1开始计数,即链表的尾节点是倒数第1个节点。
例如,一个链表有 6 个节点,从头节点开始,它们的值依次是 1、2、3、4、5、6。这个链表的倒数第 3 个节点是值为 4 的节点
示例
给定一个链表: 1->2->3->4->5, 和 k = 2. 返回链表 4->5.
样式
题解
- 思路一:倒数第k个节点,那就先求出来链表的长度,之后链表的长度减去k等于步数,比如上面的链表长度为5,k为2那么就是5-2=3,那么就是一个指针走3步就是求解的节点
- 思路二: 利用双指针进行解决
利用双指针法,定义两个指针,第一个指针先走k步,之后第一个指针和第二个指针一起走当第一个指针走到终点的时候那么第二个指针正好到了第k个位置【因为第一个指针和第二个指针一直差着k步】
- 起始第一个 first 指针先走k步
- two 指针和 first 指针一起像后面走,走到 first 指针为空的位置,此时two正好走到倒数第k个节点返回
/**
* public class ListNode {
* int val;
* ListNode next;
* ListNode(int x) { val = x; }
* }
*/
class Solution {
public ListNode getKthFromEnd(ListNode head, int k) {
ListNode first = head; // 第一个节点指针
ListNode two = head; // 第二个节点指针
// 第一个节点先走k步之后这样第一个指针与第二个指针中间差了k步
while (first != null && k > 0) {
first = first.next;
k--;
}
// 此时第一个指针和第二个指针一起走这样当第一个走到终点的时候
// 第二个正好到了倒数第k的位置
while (first != null) {
first = first.next;
two = two.next;
}
return two;
}
}
2. 环形链表
题目
给你一个链表的头节点 head ,判断链表中是否有环。
如果链表中有某个节点可以通过连续跟踪 next 指针再次到达,则链表中存在环。
如果链表中存在环 ,则返回 true 。 否则,返回 false 。
示例
输入:head = [3,2,0,-4], pos = 1
输出:true
解释:链表中有一个环,其尾部连接到第二个节点。
题解
- 思路一:其实这道题用哈希表来做的话是非常简单的
- 思路二:快慢指针操作,利用两个指针,一个是快指针一个是慢指针进行解决【重点讲解】
- 思路三:进行循环遍历,如果有环的话那么就永远到不了最有的终点,如果没有环的话那么就会遇到终点退出循环
快慢指针操作,快指针走两步,慢指针走一步,这种情况和现实生活中的操场跑步一个样子,跑的慢的人指定会被跑的快的人扣圈的,所以指定会有环的存在,也就是相交的点的存在
/**
* Definition for singly-linked list.
* class ListNode {
* int val;
* ListNode next;
* ListNode(int x) {
* val = x;
* next = null;
* }
* }
*/
public class Solution {
public boolean hasCycle(ListNode head) {
if(head == null) return false;
ListNode fast = head; // 快指针
ListNode slow = head; // 慢指针
while (fast != null && fast.next != null) {
// 快指针走两步
fast = fast.next.next;
// 慢指针走一步
slow = slow.next;
// 如果节点相等那么就证明有环的存在返回true
if (slow == fast) return true;
}
return false;
}
}
3. 回文链表
题目
给你一个单链表的头节点 head ,请你判断该链表是否为回文链表。如果是,返回 true ;否则,返回 false 。
示例
输入:head = [1,2,2,1]
输出:true
解释:链表对称所以就是回文链表
题解
正常的思路就是第一节点和最后一个节点的值相同,第二个节点和倒数第二个节点的值相同,以此后续的如果都相等的话那么就是回文链表,但是我们能获取到第一个节点但是获取不到最后一个节点,那么我们可以借助数组的这个结构,通过这个结构我们就能获取到最后面的节点
- 思路一:利用栈来解决
- 思路二:遍历所有的链表将其放入到数组中之后通过双指针来解决
那我们就用双指针进行解决,通过两个指针,一个是头指针【左边的指针】,一个是尾指针【右边的指针】之后进行两个指针的对比进行操作
/**
* Definition for singly-linked list.
* public class ListNode {
* int val;
* ListNode next;
* ListNode() {}
* ListNode(int val) { this.val = val; }
* ListNode(int val, ListNode next) { this.val = val; this.next = next; }
* }
*/
class Solution {
public boolean isPalindrome(ListNode head) {
if (head == null) return true;
List<Integer> list = new ArrayList<>();
// 将其链表中的所有节点放入到链表中
while (head != null) {
list.add(head.val);
head = head.next;
}
// 定义一个左边指针
int left = 0;
// 定义一个右边的指针
int right = list.size() - 1;
while (left <= right) {
// 如果有两边的值不相等的情况下那么就返回false
if (list.get(left) != list.get(right)) return false;
left++;
right--;
}
return true;
}
}
4. 链表的中间结点
题目
给定一个头结点为 head 的非空单链表,返回链表的中间结点。如果有两个中间结点,则返回第二个中间结点。
示例
输入:[1,2,3,4,5]
输出:此列表中的结点 3 (序列化形式:[3,4,5])
解释:中间节点是索引2的位置
题解
- 思路一:求解链表长度,根据链表的长度计算中间节点,如果链表的长度是奇数那么就返回中间节点的索引如果是偶数的话就返回中间后面的第二个索引,之后遍历链表之后进行找值
- 思路二:快慢指针,快指针走两步,慢指针走一步,当快指针到达终点的时,慢指针正好走到中间位置
/**
* Definition for singly-linked list.
* public class ListNode {
* int val;
* ListNode next;
* ListNode() {}
* ListNode(int val) { this.val = val; }
* ListNode(int val, ListNode next) { this.val = val; this.next = next; }
* }
*/
class Solution {
public ListNode middleNode(ListNode head) {
if (head == null) return null;
// 定义一个慢指针
ListNode slow = head;
// 定义一个快指针
ListNode fast = head;
while (fast != null && fast.next != null) {
// 慢指针走一步,快指针走两步,等快指针到头了那么慢指针正好到了中间节点
slow = slow.next;
fast = fast.next.next;
}
return slow;
}
}
5. 相交链表
题目
给你两个单链表的头节点 headA 和 headB ,请你找出并返回两个单链表相交的起始节点。如果两个链表不存在相交节点,返回 null 。
图示两个链表在节点 c1 开始相交:
示例
输入:intersectVal = 8, listA = [4,1,8,4,5], listB = [5,6,1,8,4,5], skipA = 2, skipB = 3
输出:Intersected at '8'
解释:相交节点的值为 8 从各自的表头开始算起,链表 A 为 [4,1,8,4,5],链表 B 为 [5,6,1,8,4,5]。在 A 中,相交节点前有 2 个节点;在 B 中,相交节点前有 3 个节点。
题解
- 思路一:其实就是利用哈希表进行操作,先将链表A 放入到哈希表中,之后再将链表放入哈希表中,如果节点已经存在那么就是有相交的节点
- 思路二:双指针操作进行一个一个比对
/**
* Definition for singly-linked list.
* public class ListNode {
* int val;
* ListNode next;
* ListNode(int x) {
* val = x;
* next = null;
* }
* }
*/
public class Solution {
public ListNode getIntersectionNode(ListNode headA, ListNode headB) {
if (headA == null || headB == null) return null;
ListNode pa = headA;
ListNode pb = headB;
while (pa != pb) {
// 因为链表长度不一样但是进行了这样指针遍历交换节点就会造成了走了相同的路
// 如果pa为空的话那么就从headB开始走
pa = pa == null ? headB : pa.next;
// 如果pb为空的话那么就从headA开始走
pb = pb == null ? headA : pb.next;
}
return pa;
}
}
小结
上面这5道题是常见的链表题目,所以通过这5到链表题目,双指针操作我估计已经入门了
上面的所有题目都出于leetcode官网