目录
1.1 单循环,两两处理,并处理各种特殊情况,未加虚拟头结点
1.3 单循环,两两处理,增加虚拟头结点,无需特殊处理尾部剩余单节点情况
1. 24. 两两交换链表中的节点【中等】
给你一个链表,两两交换其中相邻的节点,并返回交换后链表的头节点。你必须在不修改节点内部的值的情况下完成本题(即,只能进行节点交换)。
示例 1:
输入:head = [1,2,3,4] 输出:[2,1,4,3]示例 2:
输入:head = [] 输出:[]示例 3:
输入:head = [1] 输出:[1]提示:
- 链表中节点的数目在范围
[0, 100]
内0 <= Node.val <= 100
1.1 单循环,两两处理,并处理各种特殊情况,未加虚拟头结点
/**
* 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) {
// 已处理好的链末节点
ListNode end = null;
// 待处理链首节点
ListNode cur = head;
ListNode ans = null;
while (cur != null && cur.next != null) {
//暂存 下次待处理链首节点
ListNode tmp = cur.next.next;
//第二个节点指向第一个节点
cur.next.next = cur;
if (end != null) {
// 跟已处理好的链 接上
end.next = cur.next;
}
// 当前节点即第一个节点,成为已处理好的链的最后一个节点
end = cur;
if (ans == null) {
// 如果ans==null,第二个节点为答案首节点
ans = cur.next;
}
cur.next = null;
cur = tmp;
}
//处理 1个或0个节点情况
if (ans == null) {
return head;
}
//处理最后剩余一个节点情况
end.next = cur;
return ans;
}
}
1.2 递归,从后往前,两两处理(官方题解有更优雅的递归)
/**
* 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;
}
// 从后往前
ListNode ans = swapPairs(head.next.next);
ListNode tmp = head.next.next;
head.next.next = head;
ListNode t = head.next;
head.next = ans;
head = tmp;
ans = t;
return ans;
}
}
1.3 单循环,两两处理,增加虚拟头结点,无需特殊处理尾部剩余单节点情况
/**
* 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;
}
//虚拟头结点
ListNode per = new ListNode(-1,head);
ListNode cur = per;
//循环处理当前节点后两个节点
while(cur.next != null && cur.next.next != null){
//下次待处理节点
ListNode tmp = cur.next.next.next;
//第二节点指向第一节点
cur.next.next.next = cur.next;
//头结点指向第二个节点
cur.next = cur.next.next;
//此时一二节点已交换,与下次待处理节点连起来。
//注;若最后剩余一个节点,这时也已经连起来了,所以无需特殊处理
cur.next.next.next = tmp;
//将当前节点指向待处理节点上一个节点。
cur = cur.next.next;
}
return per.next;
}
}
2. 19. 删除链表的倒数第 N 个结点【中等】
给你一个链表,删除链表的倒数第
n
个结点,并且返回链表的头结点。示例 1:
输入:head = [1,2,3,4,5], n = 2 输出:[1,2,3,5]示例 2:
输入:head = [1], n = 1 输出:[]示例 3:
输入:head = [1,2], n = 1 输出:[1]提示:
- 链表中结点的数目为
sz
1 <= sz <= 30
0 <= Node.val <= 100
1 <= n <= sz
2.1 快慢指针
/**
* 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 per = new ListNode(-1, head);
ListNode slow = per, fast = per;
while (n > 0) {
fast = fast.next;
n--;
}
while (fast.next != null) {
slow = slow.next;
fast = fast.next;
}
slow.next = slow.next.next;
return per.next;
}
}
3. 面试题 02.07. 链表相交【简单】
给你两个单链表的头节点
headA
和headB
,请你找出并返回两个单链表相交的起始节点。如果两个链表没有交点,返回null
。图示两个链表在节点
c1
开始相交:题目数据 保证 整个链式结构中不存在环。
注意,函数返回结果后,链表必须 保持其原始结构 。
示例 1:
输入:intersectVal = 8, listA = [4,1,8,4,5], listB = [5,0,1,8,4,5], skipA = 2, skipB = 3 输出:Intersected at '8' 解释:相交节点的值为 8 (注意,如果两个链表相交则不能为 0)。 从各自的表头开始算起,链表 A 为 [4,1,8,4,5],链表 B 为 [5,0,1,8,4,5]。 在 A 中,相交节点前有 2 个节点;在 B 中,相交节点前有 3 个节点。示例 2:
输入:intersectVal = 2, listA = [0,9,1,2,4], listB = [3,2,4], skipA = 3, skipB = 1 输出:Intersected at '2' 解释:相交节点的值为 2 (注意,如果两个链表相交则不能为 0)。 从各自的表头开始算起,链表 A 为 [0,9,1,2,4],链表 B 为 [3,2,4]。 在 A 中,相交节点前有 3 个节点;在 B 中,相交节点前有 1 个节点。示例 3:
输入:intersectVal = 0, listA = [2,6,4], listB = [1,5], skipA = 3, skipB = 2 输出:null 解释:从各自的表头开始算起,链表 A 为 [2,6,4],链表 B 为 [1,5]。 由于这两个链表不相交,所以 intersectVal 必须为 0,而 skipA 和 skipB 可以是任意值。 这两个链表不相交,因此返回 null 。提示:
listA
中节点数目为m
listB
中节点数目为n
0 <= m, n <= 3 * 104
1 <= Node.val <= 105
0 <= skipA <= m
0 <= skipB <= n
- 如果
listA
和listB
没有交点,intersectVal
为0
- 如果
listA
和listB
有交点,intersectVal == listA[skipA + 1] == listB[skipB + 1]
进阶:你能否设计一个时间复杂度
O(n)
、仅用O(1)
内存的解决方案?
3.1 需知道如何判断是否相交
思路:若链表相交,相交点及其后节点相同。尾部对齐,从短的链的头开始比较是否相同即可。
/**
* 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) {
if (headA == null || headB == null) {
return null;
}
//1、算出链表长度
int sizeA = 1;
int sizeB = 1;
ListNode tmpA = headA;
ListNode tmpB = headB;
while (tmpA.next != null) {
tmpA = tmpA.next;
sizeA++;
}
while (tmpB.next != null) {
tmpB = tmpB.next;
sizeB++;
}
//2、都移动至倒数第n位,直至末尾,判断是否相同
tmpA = headA;
tmpB = headB;
while (tmpA != null && tmpB != null) {
if (sizeA > sizeB) {
tmpA = tmpA.next;
sizeA--;
} else if (sizeA < sizeB) {
tmpB = tmpB.next;
sizeB--;
} else {
if (tmpA == tmpB) {
return tmpA;
} else {
tmpA = tmpA.next;
tmpB = tmpB.next;
}
}
}
return null;
}
}
4. 142. 环形链表 II【中等】
给定一个链表的头节点
head
,返回链表开始入环的第一个节点。 如果链表无环,则返回null
。如果链表中有某个节点,可以通过连续跟踪
next
指针再次到达,则链表中存在环。 为了表示给定链表中的环,评测系统内部使用整数pos
来表示链表尾连接到链表中的位置(索引从 0 开始)。如果pos
是-1
,则在该链表中没有环。注意:pos
不作为参数进行传递,仅仅是为了标识链表的实际情况。不允许修改 链表。
示例 1:
输入:head = [3,2,0,-4], pos = 1 输出:返回索引为 1 的链表节点 解释:链表中有一个环,其尾部连接到第二个节点。示例 2:
输入:head = [1,2], pos = 0 输出:返回索引为 0 的链表节点 解释:链表中有一个环,其尾部连接到第一个节点。示例 3:
输入:head = [1], pos = -1 输出:返回 null 解释:链表中没有环。提示:
- 链表中节点的数目范围在范围
[0, 104]
内-105 <= Node.val <= 105
pos
的值为-1
或者链表中的一个有效索引进阶:你是否可以使用
O(1)
空间解决此题?
4.1 快慢指针、
- 判断是否有环:
- 快慢指针:快指针每步走2,慢指针每步走1,若有环,快指针先进环,慢指针后进环,快指针以1的相对速度靠近慢指针,直至追上。
- 快指针一定在慢指针转一圈内追上慢指针:因为慢指针转一圈,快指针能转两圈,中间肯定已经相遇了。
- 如何获取相遇节点:
从头结点出发一个指针,从相遇节点 也出发一个指针,这两个指针每次只走一个节点, 那么当这两个指针相遇的时候就是 环形入口的节点。
此时已经可以判断链表是否有环了,那么接下来要找这个环的入口了。
假设从头结点到环形入口节点 的节点数为x。 环形入口节点到 fast指针与slow指针相遇节点 节点数为y。 从相遇节点 再到环形入口节点节点数为 z。 如图所示:
那么相遇时: slow指针走过的节点数为:
x + y
, fast指针走过的节点数:x + y + n (y + z)
,n为fast指针在环内走了n圈才遇到slow指针, (y+z)为 一圈内节点的个数A。因为fast指针是一步走两个节点,slow指针一步走一个节点, 所以 fast指针走过的节点数 = slow指针走过的节点数 * 2:
(x + y) * 2 = x + y + n (y + z)
两边消掉一个(x+y):
x + y = n (y + z)
因为要找环形的入口,那么要求的是x,因为x表示 头结点到 环形入口节点的的距离。
所以要求x ,将x单独放在左面:
x = n (y + z) - y
,再从n(y+z)中提出一个 (y+z)来,整理公式之后为如下公式:
x = (n - 1) (y + z) + z
注意这里n一定是大于等于1的,因为 fast指针至少要多走一圈才能相遇slow指针。这个公式说明什么呢?
先拿n为1的情况来举例,意味着fast指针在环形里转了一圈之后,就遇到了 slow指针了。
当 n为1的时候,公式就化解为
x = z
,这就意味着,从头结点出发一个指针,从相遇节点 也出发一个指针,这两个指针每次只走一个节点, 那么当这两个指针相遇的时候就是 环形入口的节点。
也就是在相遇节点处,定义一个指针index1,在头结点处定一个指针index2。
让index1和index2同时移动,每次移动一个节点, 那么他们相遇的地方就是 环形入口的节点。
动画如下:
那么 n如果大于1是什么情况呢,就是fast指针在环形转n圈之后才遇到 slow指针。
其实这种情况和n为1的时候 效果是一样的,一样可以通过这个方法找到 环形的入口节点,只不过,index1 指针在环里 多转了(n-1)圈,然后再遇到index2,相遇点依然是环形的入口节点。
/**
* 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 slow = head, fast = head;
// 判断是否有环及获取相遇节点
// 相遇点
ListNode point = null;
while (fast != null && fast.next != null) {
fast = fast.next.next;
slow = slow.next;
if (fast == slow) {
point = fast;
//System.out.println(fast.val);
break;
}
}
if (point == null) {
return null;
}
// 两个指针分别从head和相遇点出发,
if (head == point) {
return head;
}
ListNode cur = head;
while (head != point) {
if (cur.next != point.next) {
cur = cur.next;
point = point.next;
} else {
break;
}
}
return cur.next;
}
}