在数组里用到双指针的思想, 可以简单有效的解决很多问题,实际上双指针只不过是两个变量而已。在链表中同样可以使用双指针来轻松解决一部分算法问题。
LeetCode876.链表的中间节点
给你单链表的头结点 head
,请你找出并返回链表的中间结点。
如果有两个中间结点,则返回第二个中间结点。
示例 1:
输入:head = [1,2,3,4,5] 输出:[3,4,5] 解释:链表只有一个中间结点,值为 3 。
示例 2:
输入:head = [1,2,3,4,5,6] 输出:[4,5,6] 解释:该链表有两个中间结点,值分别为 3 和 4 ,返回第二个结点。
直接上代码
class Solution {
public ListNode middleNode(ListNode head) {
// 定义两个指针,分别为 slow 和 fast,初始值都指向 head。
ListNode slow, fast;
slow = head;
fast = head;
// 当 fast 指针所指节点不是链表最后一个节点或倒数第二个节点时,循环继续。
while (fast.next != null && fast.next.next != null){
// 慢指针 slow 向前移动一步。
slow = slow.next;
// 快指针 fast 向前移动两步。
fast = fast.next.next;
}
// 如果链表长度是奇数,则 fast 指针所指节点是中间节点,返回 slow 即可。
if (fast.next ==null)
return slow;
// 如果链表长度是偶数,slow 指针所指节点是左边的中间节点,slow.next 是右边中间节点,因此返回 slow.next。
else
return slow.next;
}
}
上面是我自己写的
还有简洁版本,这份是大佬写的
public ListNode middleNode(ListNode head) {
ListNode slow = head, fast = head;
while (fast != null && fast.next != null) {
slow = slow.next;
fast = fast.next.next;
}
return slow;
}
面试题 02.02.返回倒数第 k 个节点
实现一种算法,找出单向链表中倒数第 k 个节点。返回该节点的值。
示例:
输入: 1->2->3->4->5 和 k = 2 输出: 4
说明:
给定的 k 保证是有效的。
直接上代码
class Solution {
public int kthToLast(ListNode head, int k) {
ListNode fast = head;
ListNode slow = head;
// 将 fast 指针向后移动 k 步
for (int i = 0; i < k; i++) {
if (fast == null)
return -1; // 如果链表长度小于 k,返回特定的错误值
fast = fast.next;
}
// 同时向后移动 fast 和 slow 指针,直到 fast 到达链表末尾
while (fast != null) { //fast即使指向null也没关系
fast = fast.next;
slow = slow.next;
}
// 返回 slow 指针所指节点的值
return slow.val;
}
}
首先将 fast
指针向后移动 k
步,然后同时向后移动 fast
和 slow
指针,直到 fast
到达链表末尾。这样,slow
指针所指的节点就是倒数第 k
个节点。
while (fast != null) 这段代码,我在思索等循环结束了,fast都指向null了,我赶紧改成了
fast.next == null。实际上有点多此一举了,指向了null就指向了呗,不会咋样,哪样简
洁哪样来。
LeetCode61.旋转链表
给你一个链表的头节点 head
,旋转链表,将链表每个节点向右移动 k
个位置。
示例 1:
输入:head = [1,2,3,4,5], k = 2 输出:[4,5,1,2,3]
示例 2:
输入:head = [0,1,2], k = 4 输出:[2,0,1]
方法一:闭合为环,首位相接,确定头节点后,再切割
代码如下
class Solution {
public ListNode rotateRight(ListNode head, int k) {
// 如果 k 为 0,或者链表为空,或者链表只有一个节点,则直接返回原链表
if (k == 0 || head == null || head.next == null) {
return head;
}
// 计算链表的长度 n
int n = 1;
ListNode iter = head;
while (iter.next != null) {
iter = iter.next;
n++;
}
// 计算实际需要移动的步数 add
int add = n - k % n;
// 如果 add 等于 n,说明不需要进行旋转,直接返回原链表
if (add == n) {
return head;
}
// 将链表首尾相连
iter.next = head;
// 找到新链表的尾节点
while (add-- > 0) {
iter = iter.next;
}
// 获取新链表的头节点,并断开链表
ListNode ret = iter.next;
iter.next = null;
return ret;
}
这段代码的作用是实现链表的右旋转操作。通过将链表的尾节点与头节点相连,然后将链表从新的尾节点处断开,得到旋转后的链表。
方法二: 双指针策略
具体思路是:
1.快指针先走k步。
2.慢指针和快指针一起走。
3.快指针走到链表尾部时,慢指针所在位置刚好是要断开的地方。把快指针指向的节点连到原链表头部,慢指针指向的节点断开和下一节点的联系。
代码如下
public ListNode rotateRight(ListNode head, int k) {
if(head == null || k == 0){
return head;
}
//这里三个变量都指向链表头结点
ListNode temp = head;
ListNode fast = head;
ListNode slow = head;
int len = 0;
//这里head先走一遍,统计出链表的元素个数,完成之后head就变成null了
while(head != null){
head = head.next;
len++;
}
if(k % len == 0){
return temp;
}
// 从这里开始fast从头结点开始向后走
//这里使用取模,是为了防止k大于len的情况
//例如,如果len=5,那么k=2和7,效果是一样的
while((k % len) > 0){
k--;
fast = fast.next;
}
// 快指针走了k步了,然后快慢指针一起向后执行
// 当fast到尾结点的时候,slow刚好在倒数第K个位置上
while(fast.next != null){
fast = fast.next;
slow = slow.next;
}
ListNode res = slow.next;
slow.next = null;
fast.next = temp;
return res;
}
总结一下
目前学到的双指针有两种主要用法
fast 指针 和 slow 指针
二者之间两种用法即对应两种关系
1.slow + k = fast
当题目给出 k 值时,先让 fast 指针先走 k 步,然后 fast 和 slow 一起走
2.fast = 2 * slow
公式的意思是,fast 指针走两步, slow 指针走一步