24. 两两交换链表中的节点
思路:正常进行模拟,需要使用虚拟头节点。由于要对链表中两个元素进行操作,便需要知道第一个元素的前一个元素,故使用虚拟头节点对头节点和第二个节点进行操作时更加方便。涉及到的交换过程如下:
class Solution {
public ListNode swapPairs(ListNode head) {
if(head == null) {
return head;
}
ListNode dummyHead = new ListNode(0, head);
ListNode cur = dummyHead;
ListNode temp = null;
ListNode temp1 = null;
while(cur.next != null && cur.next.next != null) {
temp = cur.next;
cur.next = cur.next.next;
temp1 = cur.next.next;
cur.next.next = temp;
temp.next = temp1;
cur = temp;
}
return dummyHead.next;
}
}
小结:本题涉及到的指针操作较为复杂,建议通过画图来理清元素交换的过程。重点理解交换的过程以及使用temp和temp1来暂存指向元素的指针的操作。其中对于while循环条件的判断需要结合链表中元素个数是奇数还是偶数来进行判断。
19.删除链表的倒数第N个节点
思路:本题可以使用模拟法和双指针法
- 模拟法:先通过遍历链表得到链表的长度,然后用链表的长度减去(n+1)便可以得到要删除元素的前一个元素的位置。
- 双指针法:利用快慢指针,让快指针先走n+1步,然后慢指针和快指针再同时走,当快指针等于null时,慢指针便指向了要删除元素的前一个元素的位置。该方法的理解重点在于为什么这样做就可以得到正确的结果:快指针先走了n+1步,然后再走到等于null的位置,此时快指针完整地走完了整个链表。当快指针和慢指针同时走的时候,慢指针走过的路程自然就是到要删除元素的前一个元素的位置了。
模拟法
class Solution {
public ListNode removeNthFromEnd(ListNode head, int n) {
ListNode dummyHead = new ListNode(0, head);
ListNode p = dummyHead;
ListNode q = dummyHead;
int length = 0;
while(p != null) {
p = p.next;
length++;
}
for(int i = 0; i < length - n - 1; i++) {
q = q.next;
}
q.next = q.next.next;
return dummyHead.next;
}
}
双指针法
class Solution {
public ListNode removeNthFromEnd(ListNode head, int n) {
ListNode dummyHead = new ListNode(0, head);
ListNode fast = dummyHead;
ListNode slow = dummyHead;
for(int i = 0; i < n + 1; i++) {
fast = fast.next;
}
while(fast != null) {
slow = slow.next;
fast = fast.next;
}
slow.next = slow.next.next;
return dummyHead.next;
}
}
总结:模拟法很直白,实现起来也很简单。而双指针法则需要充分利用数学关系来体现,利用我先走x,然后再走完全程L,在走完x后剩下的路程自然为L-x的思想来解决。
面试题 02.07. 链表相交
思路:使两条长度不同的链表进行长度的"统一":求出两条链表长度的差值,然后将较长的链表从其头节点开始距离差值大小的地方开始与较短的链表从其头节点开始同时遍历,寻找共同的节点。
public class Solution {
public ListNode getIntersectionNode(ListNode headA, ListNode headB) {
ListNode p = headA;
ListNode q = headB;
int length1 = 0;
int length2 = 0;
if(headA == headB) {
return headA;
}
while(p != null) {
p = p.next;
length1++;
}
while(q != null) {
q = q.next;
length2++;
}
p = headA;
q = headB;
if(length1 < length2) {
for(int i = 0; i < length2 - length1; i++) {
q = q.next;
}
while(p !=null) {
if(p == q) {
return p;
}
if(p.next == q.next) {
return p.next;
} else {
p = p.next;
q = q.next;
}
}
} else if(length1 > length2){
for(int i = 0; i < length1 - length2; i++) {
p = p.next;
}
if(p == q) {
return p;
}
while(p !=null) {
if(p.next == q.next) {
return p.next;
} else {
p = p.next;
q = q.next;
}
}
} else {
if(p == q) {
return p;
}
while(p !=null) {
if(p.next == q.next) {
return p.next;
} else {
p = p.next;
q = q.next;
}
}
}
return null;
}
}
小结:其中对于链表中交点为头节点和尾节点的情况需要增加额外的判断条件进行处理。
142.环形链表II
思路:利用在一个环中,跑的快的人一定会追上跑的慢的人的思想来处理。设置fast指针和slow指针,让fast指针的移动速度快于slow指针。当链表中存在环时,fast和slow一定会在环中相遇。若不存在环,则fast指针很快就会等于null。关于环开始位置的确定:
当fast与slow相遇时,考虑最简单的情况,此时fast走过了x+2y+z的距离,而slow走过了x+y的距离。假设fast的速度是slow速度的两倍,则有:2(x+y) = x + 2y + z,可以得到x=z。此时再从相遇点和头节点同时出发,当再次相遇时便在环形入口节点处。
public class Solution {
public ListNode detectCycle(ListNode head) {
ListNode fast = head;
ListNode slow = head;
ListNode p = head;
ListNode q;
while(fast != null && fast.next != null) {
fast = fast.next.next;
slow = slow.next;
if(fast == slow) {
q = fast;
while(p != q) {
p = p.next;
q = q.next;
}
return p;
}
}
return null;
}
}
小结:本题确定环形入口位置处需要数学结合才能更好地分析出相应的数学关系。根据相遇点到交点的距离与头节点到交点的距离相等,再次利用两个指针同时出发直到相遇便可确定环形入口位置。