LeetCode876
寻找链表的中间节点
给你单链表的头结点 head ,请你找出并返回链表的中间结点。如果有两个中间结点,则返回第二个中间结点。
思路:
- 使用“快(fast)慢(slow)指针”,使用一个快指针和指针,快指针每次向前遍历两个链表元素,而慢指针向前遍历一个。
- 快指针遍历的速度是慢指针的两倍,那么当快指针遍历到链表的尾部时候,慢指针就指向链表的中间值。
- 对于偶数个元素,为什么可以指向中间两节点的第二个呢? 对于偶数个节点的链表,由于快指针每次跳过两个节点,它会在最后一次跳跃时到达链表的最后一个节点(或 null,如果链表节点总数为奇数)。在偶数节点链表的情况下,当快指针到达最后一个节点时,慢指针恰好位于中间两个节点中的第一个。此时循环并没有结束,因为快指针还需要做最后一次移动(到达 null),慢指针则会移动到中间两个节点中的第二个。
public class MiddleNode {
private static ListNode middleNode(ListNode head) {
ListNode slow = head, fast = head;
while (slow != null && fast != null) {
slow = slow.next;
fast = fast.next.next;
}
return slow;
}
}
LeetCode61
旋转链表
给你一个链表的头节点 head ,旋转链表,将链表每个节点向右移动 k 个位置。思路:
- 首先要理解题目的最后一句话,将链表的每个节点向右移动k个位置,那就意味着从链表的最右往左数的k个元素将被移动到最左边。
- 那需要找到旋转元素的的起点,引入两个指针,一个快指针(fast)一个慢指针(slow)。
- 先让快指针向前移动k次,然后让慢指针也开始向前移动,直到快指针遍历到null为止,此时慢指针指向的就是旋转元素的起点。
- 最后将慢指针(旋转后链表的头节点)指向null,将快指针(旋转前的尾节点)指向。
package lukasy.chapter1_linklist.level2.topic4双指针;
import lukasy.chapter1_linklist.level1.ListNode;
public class RotateRight {
public static 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;
while (head != null) {
head = head.next;
len++;
}
while (k % len == 0) {
return head;
}
while ((k % len) > 0) {
k--;
fast = fast.next;
}
while (fast.next != null) {
fast = fast.next;
slow = slow.next;
}
ListNode res = slow.next;
slow.next = null;
fast.next = temp;
return res;
}
}
为什么要用k%len来判断呢?
- 旋转的周期性:如果一个链表的长度是 len,那么每旋转 len 次,链表都会恢复到其原始状态。这意味着旋转 len、2 * len、3 * len 次等等,都等同于没有旋转。
- 处理超过长度的旋转:当 k 大于 len 时,实际上只需要旋转 k % len 次就可以达到相同的效果。例如,如果链表长度是 5,旋转 7 次实际上和旋转 2 次(因为 7 % 5 = 2)的结果是一样的。
LeetCode203
给你一个链表的头节点 head 和一个整数 val ,请你删除链表中所有满足 Node.val == val 的节点,并返回 新的头节点 。
思路:
- 如果要删除当前(cur)节点,那么应该知道其前驱(pre)节点和后继(next)节点,然后让pre.next = next。
- 除此之外呢,头节点的删除处理和后面的不太一样,所以我们需要引入一个dummyNode虚拟节点,让虚拟节点指向头节点。
- 虚拟节点的用处
-
- 统一处理头节点:如果需要删除的节点恰好是头节点,那么在没有虚拟头节点的情况下,需要单独处理这种情况。使用虚拟头节点可以避免这种特殊处理,因为它为链表提供了一个固定的非空的起始点。
- 保持一致的删除逻辑:无论要删除的节点是否为头节点,处理逻辑都是一致的。这是因为虚拟头节点的引入使得每个节点(包括原始头节点)都有一个前驱节点,因此删除操作可以统一处理。
- 方便返回新的头节点:由于原始头节点可能被删除,虚拟头节点的 next 指针始终指向当前链表的头部。因此,返回新的头节点时,只需要返回 dummyHead.next 即可。
package lukasy.chapter1_linklist.level2.topic5删除元素;
import lukasy.chapter1_linklist.level1.ListNode;
public class DeletePoint {
/**
* 删除特定值的结点
*
* @param head
* @param val
* @return
*/
public static ListNode removeElements(ListNode head, int val) {
ListNode dummyHead = new ListNode(0);
dummyHead.next = head;
ListNode cur = dummyHead;
while (cur.next != null) {
if (cur.next.val == val) {
cur.next = cur.next.next;
} else {
cur = cur.next;
}
}
return dummyHead.next;
}
}
LeetCode19
给你一个链表,删除链表的倒数第 n 个结点,并且返回链表的头结点。
思路:
- 这道题目有两种思路
-
- 遍历链表的总长度,在重新遍历链表,第 L-N+1 的位置就是我们要删除的元素。
- 使用快慢指针,让快指针先向前遍历 N,然后让慢指针再开始向前遍历,当快指针遍历到链表尾节点时候,慢指针指向的就是我们要删除的节点
package lukasy.chapter1_linklist.level2.topic5删除元素;
import lukasy.chapter1_linklist.level1.ListNode;
public class DeleteBackwardsPoint {
/**
* 方法1:删除倒数第N个结点
*
* @param head
* @param n
* @return
*/
public static ListNode removeNthFromEndByLength(ListNode head, int n) {
ListNode dummy = new ListNode(0);
dummy.next = head;
int length = getLength(head);
ListNode cur = dummy;
for (int i = 1; i < length - n + 1; i++) {
cur = cur.next;
}
cur.next = cur.next.next;
return dummy.next;
}
public static int getLength(ListNode head) {
int length = 0;
while (head != null) {
++length;
head = head.next;
}
return length;
}
}
/**
* 方法3:通过双指针
*
* @param head
* @param n
* @return
*/
public static ListNode removeNthFromEndByTwoPoints(ListNode head, int n) {
ListNode dummy = new ListNode(0);
dummy.next = head;
ListNode fast = head;
ListNode slow = dummy;
for (int i = 0; i < n; i++) {
fast = fast.next;
}
while (fast != null) {
fast = fast.next;
slow = slow.next;
}
slow.next = slow.next.next;
return dummy.next;
}