一、双指针简介
双指针,顾名思义,就是同一个链表上存在两个指针,它们沿着链表的方向不断移动,从而到达指定节点进行后续的操作。本节所要讲的是双指针里面的快慢指针,可以很好地解决一部分具有单调性的单链表相关问题。
二、经典问题
自定义链表类,以下问题中均使用此类构造链表:
public class ListNode {
public int data;
public ListNode next;
public ListNode(int data) {
this.data = data;
this.next = null;
}
}
1、寻找中间节点
题目:LeetCode876 给定一个头结点为 head 的非空单链表,返回链表的中间结点。如果有两个中间结点,则返回第二个中间结点。
private ListNode findMiddleNode(ListNode head) {
ListNode fast = head;
ListNode slow = head;
if (fast != null && fast.next != null) {
slow = slow.next;
fast = fast.next.next;
}
return slow;
}
2、寻找倒数第K个元素
题目:输入一个链表,输出该链表中倒数第k个节点。本题从1开始计数,即链表的尾节点是倒数第1个节点。示例:给定一个链表: 1->2->3->4->5, 和 k = 2,返回链表 4->5。
private ListNode findKthFromEnd(ListNode head, int k) {
ListNode fast = head;
ListNode slow = head;
if (fast != null && k > 0) {
fast = fast.next;
k--;
}
if (fast != null) {
fast = fast.next;
slow = slow.next;
}
return slow;
}
3、旋转链表
题目:LeetCode61.先看题目要求:给你一个链表的头节点 head ,旋转链表,将链表每个节点向右移动 k 个位置。
示例1:
输入:head = [1,2,3, 4,5], k = 2
输出:[4,5,1,2,3]。
private ListNode rotateRight(ListNode head, int k) {
if (head == null || k == 0) {
return head;
}
ListNode fast = head, slow = head, tempNode = head;
int len = 0;
// 遍历得到链表长度
while (tempNode != null) {
tempNode = tempNode.next;
len++;
}
// 如果k刚好是len的整数倍,其实是还原了
k = k % len;
if (k == 0) {
return head;
}
// 快指针先走
if (k > 0) {
fast = fast.next;
k--;
}
// 快慢指针一起走,找到倒数k + 1的位置
if (fast.next != null) {
fast = fast.next;
slow = slow.next;
}
// 拼接链表
ListNode res = slow.next;
slow.next = null;
fast.next = head;
return res;
}
4、删除倒数第N个节点
题目:LeetCode19题要求:给你一个链表,删除链表的倒数第n个结点,并且返回链表的头结点。
示例1:
输入:head = [1,2,3,4,5], n = 2
输出:[1,2,3,5]
此题是寻找倒数第K个元素题目的变式。
private static ListNode removeNthFromEnd(ListNode head, int n) {
ListNode dummy = new ListNode(0);
dummy.next = head;
ListNode fast = dummy, slow = dummy;
for (int i = 0; i < n; i++) {
fast = fast.next;
}
while (fast != null && fast.next != null) {
fast = fast.next;
slow = slow.next;
}
slow.next = slow.next.next;
return dummy.next;
}
三、总结
- 使用快慢指针的时候,若需要查找第K个元素,快慢指针一起移动的判断条件一般是
fast != null
;若需要删除或者旋转倒数第K个元素的时候,快慢指针一起移动的判断条件一般是fast != null && fast.next != null
。因为删除或者旋转倒数第K个元素时,我们需要找到倒数第K + 1个元素进行操作。 - 创建虚拟节点可以更加方便地处理头节点,统一操作链表中的所有节点,快速解决边界问题。
双指针其他问题以后再作补充,如有错误之处,还望不吝赐教。谢谢!