文章目录
1 带环的链表
1-1 判断链表是否有环
🚀 题目链接:https://leetcode-cn.com/problems/linked-list-cycle/
🚀 代码
public boolean hasCycle(ListNode head) {
if (null == head) {
return false;
}
// 快慢指针
ListNode fast = head, slow = head;
while (null != fast && null != fast.next && null != slow) {
// 快指针移动两步
fast = fast.next.next;
// 慢指针移动一步
slow = slow.next;
if (fast == slow) {
// 两个指针相遇,说明有环
return true;
}
}
return false;
}
1-2 求带环链表的环入口
🚀 题目链接:https://www.acwing.com/problem/content/86/
🚀 代码
public ListNode entryNodeOfLoop(ListNode head) {
if (null == head) {
return null;
}
// 快慢指针
ListNode fast = head, slow = head;
while (null != fast && null != fast.next && null != slow) {
fast = fast.next.next;
slow = slow.next;
if (fast == slow) {
// 快慢指针相遇,再从头移动
ListNode temp = head;
while (null != temp && null != slow) {
// 两个同步指针一起移动
temp = temp.next;
slow = slow.next;
if (temp == slow) {
// 同步指针相遇的位置就是环的起点
return slow;
}
}
}
}
// 找不到环,返回null
return null;
}
2 删除指定节点/元素
2-1 单链表删除,给出头节点
public void deleteNode(ListNode head, ListNode target) {
if (null == head) {
return ;
}
// 如果目标值就是头节点,则直接返回下一个节点即可
if (head == target) {
return head.next;
}
ListNode p = head;
// 头节点是目标值已排除,直接从next开始,方便删除
while (null != p.next) {
if (p.next == target) {
p.next = p.next.next;
return ;
}
p = p.next;
}
}
2-2 单链表删除,只给出待删除的节点
🚀 题目链接:https://www.acwing.com/problem/content/85/
🚀 代码
// 前提:不知道前一个节点、不需要删除整个节点对象(地址)、待删除节点不是尾节点
public void deleteNode(ListNode node) {
// 记住下一个节点
ListNode nextNode = node.next;
// 将后面的节点值复制给当前节点
node.val = nextNode.val;
// 再删除下一个节点即可(当前节点指向下下一个节点)
node.next = nextNode.next;
}
2-3 双向链表删除
public void deleteNode(DoubleListNode node) {
// 前节点的后节点指向当前节点的后节点
node.pre.next = node.next;
// 后节点的前节点指向当前节点的前节点
node.next.pre = node.pre;
}
2-4 删除重复元素,保留1个重复元素
🚀 题目链接:https://leetcode-cn.com/problems/remove-duplicates-from-sorted-list/
🚀 代码
public ListNode deleteDuplicates(ListNode head) {
if (null == head) {
return null;
}
ListNode p = head;
while (null != p && null != p.next) {
if (p.val == p.next.val) {
// 当前节点与下一个节点值相同,直接删除下一个节点,不移动p
p.next = p.next.next;
} else {
// 不相同,正常移动p
p = p.next;
}
}
return head;
}
2-5 删除重复元素,不保留重复元素
🚀 题目链接:https://www.acwing.com/problem/content/27/
🚀 代码
public ListNode deleteDuplicates(ListNode head) {
if (null == head) {
return null;
}
// 创建一个虚拟头节点,这里初始化的val值不能与原链表的头节点值相等
ListNode headPre = new ListNode(-1);
headPre.next = head;
ListNode p = headPre;
while (null != p.next) {
// 遍历重复元素
ListNode q = p.next;
while (null != q && p.next.val == q.val) {
// 依次与p.next比较,有重复元素就继续移动
q = q.next;
}
if (p.next.next == q) {
// 如果q只移动了一格,说明没有与p.next重复的元素
// p正常移动
p = p.next;
} else {
// 如果q移动了不止一格,则存在重复元素,直接删除
p.next = q;
}
}
// 返回虚拟头节点的下一个节点
return headPre.next;
}
3 反转链表
3-1 整条链反转
🚀 题目链接:https://www.acwing.com/problem/content/33/
🚀 代码
-
迭代版本
public ListNode reverseList(ListNode head) { if (null == head) { return null; } // 前后指针 ListNode pre = null, curr = head; while (null != curr) { // 头指针先移动 head = head.next; // 当前指针下一个节点指向pre curr.next = pre; // 修改pre和curr pre = curr; curr = head; } // 最后返回pre,不是curr/head return pre; }
-
递归版本
public ListNode reverseList(ListNode head) { if (null == head) { return null; } // 递归 return dfs(null, head); } // 递归反转链表 private ListNode dfs(ListNode pre, ListNode curr) { if (null == curr) { return pre; } // 记住下一个节点 ListNode nextNode = curr.next; // 修改当前节点下一个节点为前节点 curr.next = pre; // 递归继续修改下一个节点 return dfs(curr, nextNode); }
3-2 在指定区间内反转
🚀 题目链接:https://www.nowcoder.com/practice/b58434e200a648c589ca2063f1faf58c
🚀 代码
// 只反转[m,n]区间的链表,链表节点索引从1开始
public ListNode reverseBetween(ListNode head, int m, int n) {
if (null == head) {
return null;
}
// 因为反转的可能是head,所以需要虚拟头节点
ListNode headPre = new ListNode(-1, head);
// 辅助指针,pre指向m-1位置的节点,p指向要反转的链表头节点
ListNode pre = headPre, p = head;
// 记住(m,n)之间的节点数(不包括m和n)
int diff = n - m;
// 移动到第m个节点,如果m=1就不用移了
while (--m > 0) {
pre = p;
p = p.next;
}
// 依次反转[m,n]之间的节点
while (diff-- > 0) {
// 辅助指针,记住p.next,将p和temp两个节点反转
ListNode temp = p.next;
// p下一个节点指向temp的下一个节点
p.next = temp.next;
// temp下一个节点再指向pre的下一个节点(pre始终在m-1的位置)
// 注意这里不能写temp.next = p,因为p是一直往后移动,pre始终在m-1的位置,故pre后面不一定是p
temp.next = pre.next;
// pre的下一个节点指向temp,完成p和temp的反转
pre.next = temp;
}
return headPre.next;
}
4 链表排序
相关:常见排序算法
4-1 合并两个有序链表
🚀 题目链接:https://leetcode-cn.com/problems/he-bing-liang-ge-pai-xu-de-lian-biao-lcof/comments/
🚀 代码
public ListNode merge(ListNode l1, ListNode l2) {
if (null == l1 && null == l2) {
return null;
}
// 分别指向两个链表的指针
ListNode p1 = l1, p2 = l2;
// 创建新的链表
ListNode newHead = new ListNode(-1), p = newHead;
// 一次归并排序
while (null != p1 && null != p2) {
if (p1.val < p2.val) {
p.next = p1;
p1 = p1.next;
} else {
p.next = p2;
p2 = p2.next;
}
p = p.next;
}
if (null != p1) {
p.next = p1;
} else {
p.next = p2;
}
return newHead.next;
}
4-2 排序链表
🚀 题目链接:https://leetcode-cn.com/problems/sort-list/
🚀 代码
PS:这里涉及的归并排序
merge()
用上面👆的就行
- 迭代版本
public ListNode sortList(ListNode head) { if (null == head) { return null; } // 先求出链表长度 int len = 0; ListNode h = head; while (null != h) { h = h.next; len++; } // 虚拟头节点,指向head ListNode headPre = new ListNode(-1, head); // 外循环subLen逐渐倍增,直到等于len for (int subLen = 1;subLen < len;subLen <<= 1) { // 记住此时虚拟节点,并初始化curr(初始化指向第一个节点) ListNode pre = headPre, curr = headPre.next; while (null != curr) { // 记住当前curr为子链表①的头节点 ListNode head1 = curr; // 移动subLen-1个位置 for (int i = 1;i < subLen && null != curr.next;i++) { curr = curr.next; } // 从此时的curr断开,curr下一个节点作为子链表②的头节点 // 前面循环条件确保了curr不为null,所以这里可以直接写head2 = curr.next ListNode head2 = curr.next; curr.next = null; curr = head2; // 再移动subLen-1个位置 for (int i = 1;i < subLen && null != curr && null != curr.next;i++) { curr = curr.next; } // 再从此时的curr断开,curr下一个节点作为新头节点 // 这里不能直接将curr.next赋值给newHead,因为curr可能为null ListNode newHead = null; if (null != curr) { newHead = curr.next; curr.next = null; } // 合并排序两个子链表,合并后的链表拼接到pre后面 pre.next = merge(head1, head2); // pre再移动到最后一个节点 while (null != pre.next) { pre = pre.next; } // 更新下一个curr curr = newHead; } } return headPre.next; }
- 递归版本
public ListNode sortList(ListNode head) { if (null == head || null == head.next) { // 如果头节点为null或只有一个节点,要返回这个head,不能返回null return head; } // 快慢指针将链表等分 ListNode fast = head, slow = head; // 注意这里结束条件是fast.next和fast.next.next /// 保证fast不会移动到null,也就是保证fast不会移动到链表外面 // 如果fast移动到链表外,slow指向的节点就不是中间那个节点 while (null != fast.next && null != fast.next.next) { fast = fast.next.next; slow = slow.next; } // 断开两条长度一样的链 ListNode head2 = slow.next; slow.next = null; // 递归返回排序后的链表,然后再合并 return merge(sortList(head), sortList(head2)); }