【链表总结】
1、链表的特性:查慢改快
2、虚拟头节点:解决链表题的重要技巧,用于避免对头节点的特殊处理
3、建议的链表题刷题顺序:
707.设计链表 - 掌握节点的定义、查询、插入和删除操作,熟悉虚拟头节点的技巧
206.反转链表、24.两两交换链表中的节点 - 掌握节点交换和节点遍历
142.环形链表II(结合数学证明) - 掌握快慢指针的巧妙应用
【24. 两两交换链表中的节点 】
方法 三指针法
1、特殊情况处理:如果链表为空或者只有1个节点,则直接返回head
2、基本情况处理:当链表中含有2个及以上节点时,返回值一定是head.next
- 交换节点
涉及三个节点,前节点pre、当前节点cur、后节点after
- 继续遍历
需要注意,步骤一后,cur和after在链表中的顺序发生变化,重新赋值时要谨慎处理
/**
* 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 swapPairs(ListNode head) {
if (head == null || head.next == null) return head; // 如果0个/1个节点,直接返回head
ListNode target = head.next;
ListNode pre = new ListNode(-1, head);
ListNode cur = head;
ListNode after = cur.next;
// 将当前节点cur与下一个节点after进行交换(如果只剩一个节点就不进入循环)
while (cur != null && after != null){
// 交换节点
cur.next = after.next;
after.next = cur;
pre.next = after;
// 继续遍历
if (cur.next == null){ // 后面没节点了
break;
}
else{ // 后面还有节点
pre = cur;
cur = pre.next;
after = cur.next;
}
}
return target;
}
}
时间复杂度: O(n)
空间复杂度: O(1)
【19.删除链表的倒数第N个节点 】
方法一 两次for循环
第一次for循环:计算节点的个数size
第二次for循环:令cur指向目标节点的上一个节点,删除目标节点
/**
* 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 removeNthFromEnd(ListNode head, int n) {
ListNode prehead = new ListNode(-1, head);
ListNode cur = head;
// 计算节点的数量
int size = 0;
while (cur != null){
size++;
cur = cur.next;
}
// 找到要删除节点的前一个节点
cur = prehead;
for (int i = 0; i < size - n; i++){
cur = cur.next;
}
cur.next = cur.next.next; // 删除目标节点
return prehead.next;
}
}
时间复杂度: O(n)
空间复杂度: O(1)
方法二 快慢指针(双指针技巧)
思路:
1、快指针先走n+1步,目的是最后让slow指向目标节点的前一个节点
2、快慢指针一起走,直至快指针指向末尾的null
![]()
/**
* 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 removeNthFromEnd(ListNode head, int n) {
ListNode prehead = new ListNode(-1, head);
ListNode fast = prehead;
ListNode slow = prehead;
// 让快指针先走n+1步,则最后slow会在倒数第n个节点的上一个节点
for (int i = 0; i < n + 1; i++)
{
fast = fast.next;
}
// 快慢指针一起走,直到快指针指向末尾的null
while(fast != null){
fast = fast.next;
slow = slow.next;
}
// 删除节点
slow.next = slow.next.next;
return prehead.next;
}
}
时间复杂度: O(n)
空间复杂度: O(1)
【面试题 02.07. 链表相交 】
方法 双指针
思路:尾部对齐,从重叠的第一个节点同时开始遍历
1、分别计算两个链表的长度
2、链表长的节点先走delta步(delta是指长度差)
3、遍历相同个数的节点,寻找相交的节点
注意:相交节点是指内存地址相同的节点,不是值相同的节点
/**
* 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) {
ListNode curA = headA;
ListNode curB = headB;
// 分别计算两个链表的长度
int sizeA = 0;
int sizeB = 0;
while (curA != null){
sizeA++;
curA = curA.next;
}
while (curB != null){
sizeB++;
curB = curB.next;
}
// 链表长的节点先走delta步
int delta = Math.abs(sizeA - sizeB);
curA = headA;
curB = headB;
if (sizeA > sizeB){
for (int i = 0; i < delta; i++){
curA = curA.next;
}
}
else{
for (int i = 0; i < delta; i++){
curB = curB.next;
}
}
// 遍历相同个数的节点,寻找相交的节点
while (curA != null){
if (curA == curB){
return curA;
}
curA = curA.next;
curB = curB.next;
}
return null;
}
}
时间复杂度: O(m+n),m是链表A的长度,n是链表B的长度
空间复杂度: O(1)
【142.环形链表II 】
方法 快慢指针
如何证明 x = z ?点此处查看
思路:
1、确定是否有环
无环:fast到达null,返回null
有环:fast和slow相遇,记录第一次相遇的位置index
2、利用 x = z 找出环开始的节点
令一个指针从链表头head出发’(走x),另一个指针从相遇节点index出发(走z)
两个指针相遇的位置,就是环开始的节点
/**
* Definition for singly-linked list.
* class ListNode {
* int val;
* ListNode next;
* ListNode(int x) {
* val = x;
* next = null;
* }
* }
*/
public class Solution {
public ListNode detectCycle(ListNode head) {
ListNode fast = head;
ListNode slow = head;
ListNode index = null;
while (fast != null && fast.next != null){
fast = fast.next.next;
slow = slow.next;
// 如果相遇,让index指向相遇的节点
if (fast == slow){
index = fast;
break;
}
}
// 如果快慢指针没相遇,则index未被赋值,依然为null,即无环,返回null
if (index == null) {
return null;
}
// 有环,继续寻找环的开始节点
slow = head;
fast = index;
while (slow != fast){
slow = slow.next;
fast = fast.next;
}
// while循环结束后,slow=fast,快慢指针相遇,刚好是环开始的节点,返回fast/slow即可
return fast;
}
}
时间复杂度: O(n)
空间复杂度: O(1)