24. 两两交换链表中的节点
思路: 不难,画图模拟两部分操作,即交换结点和更新指针。注意两点:避免断链(交换顺序合理) + 避免空指针异常(明确退出循环的条件)
class Solution {
public ListNode swapPairs(ListNode head) {
if(head == null || head.next == null) return head;
ListNode L = new ListNode(-1, head);
ListNode pre = L, n1 = head, n2 = head.next;
// pre->n1->n2->n3->n4->...
// 交换:n1->n3,n2->n1,pre->n2 变成 pre->n2->n1->n3->n4->...
// 更新:pre(n1),n1(n3),n2(n4)
while(n2 != null) {
//交换
n1.next = n2.next;
n2.next = n1;
pre.next = n2;
//更新
pre = n1;
n1 = n1.next;
//注意,若n1此时为null,表示本次交换后只有最后一个结点了,不需要交换跳出即可
//若不跳出,n2赋值将会报空指针异常
if(n1 == null) break;
n2 = n1.next;
}
return L.next;
}
}
递归版本:
class Solution {
public ListNode swapPairs(ListNode head) {
// base case 退出提交
if(head == null || head.next == null) return head;
// 获取当前节点的下一个节点
ListNode next = head.next;
// 进行递归
ListNode newNode = swapPairs(next.next);
// 这里进行交换
next.next = head;
head.next = newNode;
return next;
}
}
19.删除链表的倒数第N个节点
思路: 双指针的经典应用,如果要删除倒数第n个节点,让fast先移动n步,然后让fast和slow同时移动,直到fast指向链表末尾。删掉slow所指向的节点就可以了。
删除链表结点的关键在于找到所删结点的前一个结点
步骤
- 定义fast指针和slow指针,初始指向虚拟头结点;
- fast首先走n + 1步 ,这样fast和slow之间间隔n个节点,然后同时移动,等fast指向null时,slow最终指向删除节点的上一个节点(方便做删除操作);
- fast和slow同时移动,直到fast指向末尾;
- 删除slow指向的下一个节点
class Solution {
public ListNode removeNthFromEnd(ListNode head, int n){
ListNode L = new ListNode(-1, head);
ListNode fastIndex = L, slowIndex = L;
//只要快慢指针距离相差n即可
for (int i = 0; i <= n ; i++){//fast先行n+1步
fastIndex = fastIndex.next;
}
while (fastIndex != null){//直到fast最终指向null
fastIndex = fastIndex.next;
slowIndex = slowIndex.next;
}
//此时 slowIndex 的位置就是待删除元素的前一个位置。
// n=2,起始:L(fast,slow)->1->2->3->null
// fast移动后:L(slow)->1->2->3(fast)->null
// 同时移动后:L->1(slow)->2->3->null(fast)
slowIndex.next = slowIndex.next.next;
return L.next;
}
}
面试题 02.07. 链表相交
思路: 注意,交点不是数值相等,而是指针相等。关键思路在于,两条链表若相交,交点之后的部分合二为一。(因为链表节点不能一对多,交点.next只能指向同一个结点)
- 哈希集合
首先遍历链表 headA,并将链表 headA 中的每个节点加入哈希集合中。然后遍历链表 headB,对于遍历到的每个节点,判断该节点是否在哈希集合中:- 如果当前节点不在哈希集合中,则继续遍历下一个节点;
- 如果当前节点在哈希集合中,则后面的节点都在哈希集合中,即从当前节点开始的所有节点都在两个链表的相交部分,因此在链表 headB 中遍历到的第一个在哈希集合中的节点就是两个链表相交的节点,返回该节点。
- 如果链表 headB 中的所有节点都不在哈希集合中,则两个链表不相交,返回 null。
class Solution {//HashSet存储结点
public ListNode getIntersectionNode(ListNode headA, ListNode headB) {
Set<ListNode> set = new HashSet<>();
ListNode LA = headA, LB = headB;
while(LA != null) { //将A链表节点存入hashset
set.add(LA);
LA = LA.next;
}
while(LB != null) {
if(set.contains(LB)) return LB;
LB = LB.next;
}
return null;
}
}
- 对应位置指针遍历
由于链表长度可能不同,都从头节点开始的话就无法对照着遍历判断。因此,考虑先将两条链表末尾对齐,求出两个链表长度的差值,让长链表先移动到与短链表相对应的位置,然后一起向后遍历寻找交点。
public class Solution {
public ListNode getIntersectionNode(ListNode headA, ListNode headB) {
ListNode curA = headA;
ListNode curB = headB;
int lenA = 0, lenB = 0;
while (curA != null) { // 求链表A的长度
lenA++;
curA = curA.next;
}
while (curB != null) { // 求链表B的长度
lenB++;
curB = curB.next;
}
curA = headA;
curB = headB;
// 让curA为最长链表的头,lenA为其长度
if (lenB > lenA) {
//1. swap (lenA, lenB);
int tmpLen = lenA;
lenA = lenB;
lenB = tmpLen;
//2. swap (curA, curB);
ListNode tmpNode = curA;
curA = curB;
curB = tmpNode;
}
// 求长度差
int gap = lenA - lenB;
// 让curA和curB在同一起点上(末尾位置对齐)
while (gap-- > 0) {
curA = curA.next;
}
// 遍历curA 和 curB,遇到相同则直接返回
while (curA != null) {
if (curA == curB) {
return curA;
}
curA = curA.next;
curB = curB.next;
}
return null;
}
}
142.环形链表II
思路:
-
判断链表是否有环:可以使用快慢指针法,分别定义 fast 和 slow 指针,从头结点出发,fast指针每次移动两个节点,slow指针每次移动一个节点,如果 fast 和 slow指针在途中相遇 ,说明这个链表有环。(在有环的情况下,相对于slow来说,fast是一个节点一个节点的靠近slow的,所以fast一定可以和slow重合)
-
寻找环的入口:如下图
两指针相遇时,slow指针走过的节点数为: x + y,fast指针走过的节点数:x + y + n (y + z),n为fast指针在环内走的圈数,(y+z)为一圈内的节点个数。因为fast指针是一步走两个节点,slow指针一步走一个节点,所以 fast走过的节点数是slow走过的节点数二倍: (x + y) * 2 = x + y + n (y + z),即 x + y = n (y + z)
因为要找环形的入口,那么要求的是x:x = n (y + z) - y
再从n(y+z)中提出一个 (y+z),整理公式之后:x = (n - 1) (y + z) + z ( n>=1,因为 fast指针至少要多走一圈才能相遇slow指针。
这个公式说明:从相遇点到入环点的距离加上 n−1 圈的环长,恰好等于从链表头部到入环点的距离。 因此,当发现 slow 与 fast 相遇时,我们从头结点出发一个指针,从相遇节点也出发一个指针,这两个指针每次只走一个节点, 那么当这两个指针相遇的时候就是环形入口的节点。
public class Solution {
public ListNode detectCycle(ListNode head) {
if(head == null || head.next == null) return null; //一个节点无法成环
ListNode slow = head;
ListNode fast = head;
while (fast != null && fast.next != null) {
slow = slow.next;
fast = fast.next.next;
if (slow == fast) {// 说明有环
ListNode index1 = fast;
ListNode index2 = head;
// 两个指针,从头结点和相遇结点,各走一步,直到相遇,相遇点即为环入口
while (index1 != index2) {
index1 = index1.next;
index2 = index2.next;
}
return index1;
}
}
return null;
}
}