24. 两两交换链表中的节点
题目详细:LeetCode.24
Java解法(递归):
class Solution {
public ListNode swapPairs(ListNode head) {
if(head == null || head.next == null)
return head;
ListNode next = head.next;
head.next = swapPairs(next.next);
next.next = head;
return next;
}
}
19.删除链表的倒数第N个节点
题目详细:LeetCode.19
Java解法(双指针(快慢指针)):
class Solution {
public ListNode removeNthFromEnd(ListNode head, int n) {
ListNode ans = new ListNode(-1, head), p_del = ans, p_last = ans;
while(0 < n--){
p_last = p_last.next;
}
while(p_last.next != null){
p_del = p_del.next;
p_last = p_last.next;
}
p_del.next = p_del.next.next;
return ans.next;
}
}
面试题 02.07. 链表相交
题目详细:LeetCode.面试题 02.07.
这道题算是很常见的面试题了,难度也比较简单,当看到查找两个链表相交的节点时,我第一个蹦出来的解题方法就是利用哈希表,只要遍历一个链表并用哈希表进行记录,再遍历另一个链表,看看哈希表中是否已存在同样的记录,如果出现相同记录,则该记录的节点为两个链表的公共交点。
Java解法(哈希表):
public class Solution {
public ListNode getIntersectionNode(ListNode headA, ListNode headB) {
HashSet<ListNode> set = new HashSet<>();
while(headA != null){
set.add(headA);
headA = headA.next;
}
while(headB != null){
if(!set.add(headB)){
return headB;
}
headB = headB.next;
}
return null;
}
}
这道题也可以使用队列来解决,但它们两的解决思路是殊途同归的,时间复杂度和空间复杂度都是 O(n+m),于是我猜想能不能利用双指针实现空间复杂度为 O(1) 的算法。
但是才学疏浅的我虽然知道可以利用双指针来解决,但是却不知道如何实现,看了题解之后才大彻大悟:
在这里分享一个来自LeetCode玩家的题解:【图解】利用双指针解题
Java解法(双指针):
public class Solution {
public ListNode getIntersectionNode(ListNode headA, ListNode headB) {
ListNode pA = headA, pB = headB;
while(pA != pB){
pA = pA != null ? pA.next : headB;
pB = pB != null ? pB.next : headA;
}
return pA;
}
}
142.环形链表II
题目详细:LeetCode.142
题目要求找出环形链表中环的入口节点,这和上一题找出两个链表的相交节点有一曲同工之妙,也是可以利用哈希表来记录已经遍历过的节点:
- 特判:当链表为空或者链表中只有一个节点时,不属于环形链表,直接返回null。
- 当链表中有环时,只要一直遍历链表并且利用哈希表进行记录,直到找到重复记录的节点并返回即可;
- 当链表中没有环时,在链表遍历结束后,用于遍历链表的指针head将指向null。
Java解法(哈希表):
public class Solution {
public ListNode detectCycle(ListNode head) {
// 链表为空或链表中没有环
if(head == null || head.next == null){
return null;
}
// 使用哈希表记录遍历过的节点
HashSet<ListNode> set = new HashSet<>();
// 当链表中有环时,一定能找到重复记录的节点
while(head != null){
if(!set.add(head)){
// 重复记录的节点则为环的入口节点
return head;
}
head = head.next;
}
// 当链表中无环时,链表遍历结束后head指向null
return head;
}
}
如果不画图的话,还真难发现这道题也可以利用双指针来解决,通过定义快慢两个指针并构建两轮相遇的过程,来找出环的入口节点:
- 构建第一轮相遇:
- 如果快指针为null,则说明链表不属于环形链表或链表为空
- 如果快慢两个指针
在第一轮遍历过程中,能够相遇,则能够确定该链表存在环,但不能确定相遇的节点是否为环的入口节点
,还需要构建第二轮相遇来找出环的入口节点
- 构建第二轮相遇:
- 将快指针重新赋值,使其指向链表的头节点,此时我们可以发现,
快慢指针分别指向的两个节点,它们到达环的入口节点的距离恰好是相等的
。 - 所以同时遍历快慢两个指针,直到两者再次相遇,那么相遇的节点则为环的入口节点
- 将快指针重新赋值,使其指向链表的头节点,此时我们可以发现,
但是我觉得我不能很好地描述解题的思路和过程,所以这里贴出LeetCode玩家的题解,它以画图的形式来解释快慢指针两轮相遇的过程,我觉得更加全面易懂:【图解】利用双指针解题
Java解法(双指针):
public class Solution {
public ListNode detectCycle(ListNode head) {
ListNode slow = head, fast = head;
// 构建第一轮相遇
while(true){
if(fast == null || fast.next == null){
return null;
}
slow = slow.next;
fast = fast.next.next;
// 如果双指针第一轮能够相遇则确定该链表存在环,但不能确定相遇的节点是环的入口节点
if(slow == fast) break;
}
fast = head;
// 到了这一步,此时我们可以发现,快慢指针分别指向的两个节点,它们到达环的入口节点的距离恰好是相等的
// 所以同时遍历快慢两个指针,直到两者再次相遇,那么相遇的节点则为环的入口节点
while (slow != fast) {
slow = slow.next;
fast = fast.next;
}
return fast;
}
}
总结
经过多次练习链表相关的题目后,发现在解题过程中,经常定义一个虚拟头节点并连接表头后,再对链表进行后续的操作,所以这里总结一下虚拟头节点的作用:
【有空再总结】
先仅作题打卡,解析后续有空再补上,每逢佳节实在是太忙碌了,但是尽管再忙,时间再少,也希望每天都能做到“今日事今日毕”,所以用此诗作结吧:
世人若被明日累, 春去秋来老将至。