目录
该题需要弄清楚如何翻转节点(cur指针的位置)以及判断反转结束的条件
public class SwapPairs {
/**
* 虚拟头节点法
*
* @param head
* @return 头节点
*/
public ListNode dummyHeadMethod(ListNode head) {
ListNode dummy = new ListNode(0);
dummy.next = head;
// 对于操作的节点需要一个cur指针指向它的上一个节点,用于记录原链表节点的顺序
ListNode cur = dummy;
ListNode tmp1, tmp2;
// 两两交换操作需要满足偶数位节点不为null,同时也要保证不会空指针报错
while (cur.next != null && cur.next.next != null) {
// 记录原链表节点的指针
tmp1 = cur.next;
tmp2 = cur.next.next.next;
// 交换
cur.next = cur.next.next;
cur.next.next = tmp1;
tmp1.next = tmp2;
// 移动cur指针,为下一轮交换做准备
cur = tmp1;
}
return dummy.next;
}
}
递归方法后补
19. 删除链表的倒数第 N 个结点
双指针法,利用快慢指针的差来找到倒数第N个节点
虚拟头节点,看起来涉及到链表的都需要
public class RemoveNthFromEnd {
/**
* 快慢指针法(利用快慢指针步差为n,来找到倒数第N个节点,操作节点必须要让指针指向上一个节点,因而步差为n+1)
*
* @param head
* @param n 倒数第n个节点
* @return
*/
public ListNode twoPointersMethod(ListNode head, int n) {
ListNode dummy = new ListNode(0);
dummy.next = head;
// 快指针用来找到链表的尾部
ListNode fast = dummy;
// 慢指针用来找到删除节点的上一个节点
ListNode slow = dummy;
// 快指针要比慢指针先多走N+1步,这样才能找到倒数第N+1个节点来删除第N个节点
while (n-- >= 0 && fast != null) {
fast = fast.next;
}
// 此时快慢指针同时移动
while (fast != null) {
fast = fast.next;
slow = slow.next;
}
// 此时快指针已指向null,slow指针则指向要删除节点的上一个节点
// 删除操作
// 避免空指针异常
if (slow.next != null) {
slow.next = slow.next.next;
}
// 返回新头节点
return dummy.next;
}
}
面试题 02.07. 链表相交
由于链表的节点只能指向一个节点的特性,链表相交后的节点必然都是同一个,把它想象成相汇会更好理解。
public class IntersectionOfTwoLinkedLists {
/**
* 先移动长链表,实现同节点同步移动来找到相交节点
*
* @param headA
* @param headB
* @return
*/
public ListNode getIntersectionNode(ListNode headA, ListNode headB) {
// 获得两链表的长度
ListNode curA = headA;
ListNode curB = headB;
int lenA = 0;
int lenB = 0;
while (curA != null) {
lenA++;
curA = curA.next;
}
while (curB != null) {
lenB++;
curB = curB.next;
}
curA = headA;
curB = headB;
// 使得curA为最长链表的头节点,lenA为最长链表的长度,方便统一后续操作
if (lenB > lenA) {
// 交换len
int tmpLen = lenB;
lenB = lenA;
lenA = tmpLen;
// 交换cur
ListNode tmpNode = curB;
curB = curA;
curA = tmpNode;
}
// 求长度差,为了让他们在同一节点(使得末尾对齐,必然是末尾对齐的)
int gap = lenA - lenB;
while (gap-- > 0) {
curA = curA.next;
}
// 同步移动,相同节点则直接返回
while (curA != null) {
if (curA == curB) {
return curA;
}
curA = curA.next;
curB = curB.next;
}
return null;
}
/**
* 合并链表,同步移动,终会同步到达相交点
*
* @param headA
* @param headB
* @return
*/
public ListNode combineLinkedListMethod(ListNode headA, ListNode headB) {
ListNode p1 = headA, p2 = headB;
while (p1 != p2) {
// p1和p2从各自的头节点开始同步移动
if (p1 == null) {
// 走完原链表,则走对方链表
p1 = headB;
} else {
p1 = p1.next;
}
if (p2 == null) {
p2 = headA;
} else {
p2 = p2.next;
}
}
return p1;
}
}
142.环形链表II
环形链表主要考察:
- 如何判断有环(双指针)
- 如何找到环形链表入口处
public class LinkedListCycleII {
/**
* 利用快慢指针判断是否有环,双指针相遇找到环入口
*
* @param head
* @return
*/
public ListNode detectCycle(ListNode head) {
// 假设快指针每次走两个节点
ListNode fast = head;
// 慢指针每次只走一个节点
ListNode slow = head;
while (fast != null && fast.next != null) {
fast = fast.next.next;
slow = slow.next;
// 快慢指针相遇(类似于追及问题)
// 快指针走过的路程 = 2倍的慢指针走过的路程
// 2(x+y)= x+n(y+z)+y; -> x = (n-1)(z+y)+z;
// 假设只走了一圈就相遇了,那么可以推出 x=z
if (fast == slow) {
// 两个节点各自从fast和head开始走,必定相遇在环形入口节点
ListNode cur1 = fast;
ListNode cur2 = head;
while (cur1 != cur2) {
cur1 = cur1.next;
cur2 = cur2.next;
}
return cur1;
}
}
return null;
}
}
链表总结
链表的特性:通过指针将内存中分散的节点串联。
虚拟头节点的技巧,可以避免对头节点的单独处理。
双指针是个好方法,用得巧可以解决很多问题!