24.两两相交链表中的节点
题目链接:24. 两两交换链表中的节点 - 力扣(LeetCode)
该题目本文将以普通循环方式以及递归的方式来进行解题
添加头结点的方式:
public ListNode swapPairs(ListNode head) {
// 添加头结点
if(head == null || head.next == null) return head;
ListNode nHead = new ListNode(-1,head);
ListNode p = nHead;
while(p.next != null && p.next.next != null){
ListNode q = p.next;
ListNode r = p.next.next;
q.next = r.next;
p.next = r;
r.next = q;
p = q;
}
return nHead.next;
}
不添加头结点的方式:
public ListNode swapPairs(ListNode head) {
// 不添加虚拟头节点的写法
if(head == null || head.next == null) return head;
ListNode p = head.next;
head.next = p.next;
p.next = head;
ListNode q = p.next;
while(q.next != null && q.next.next != null){
ListNode n = q.next;
ListNode nn = q.next.next;
n.next = nn.next;
q.next = nn;
nn.next = n;
q = n;
}
return p;
}
递归方式:
public ListNode swapPairs(ListNode head) {
// 递归方式,前提:能够将问题分解为相同的较小的子问题
if(head == null || head.next == null) return head;
ListNode node1 = head;
ListNode node2 = head.next;
ListNode node3 = swapPairs(head.next.next);
node2.next = node1;
node1.next = node3;
return node2;
}
19.删除链表的倒数第N个节点
题目链接:代码随想录 (programmercarl.com)
这个题目一共两种解题思路:笨方法:遍历整个链表,看具体有多少个结点,再根据节点数判断要删除结点的正向位置,最差需要遍历链表。
public ListNode removeNthFromEnd(ListNode head, int n) {
if(head.next == null) return null;
int m = 0;
ListNode p = head;
while(p != null){
p = p.next;
m++;
}
int time = m - n - 1;
if(time == -1) return head.next;
p = head;
while(time > 0){
p = p.next;
time--;
}
p.next = p.next.next;
return head;
}
方法二:双指针法,该方法利用双指针之间的距离差,可以在只遍历一次的情况下确定要删除元素的位置。
public ListNode removeNthFromEnd(ListNode head, int n) {
// 设一个虚拟头结点
ListNode nHead = new ListNode(-1,head);
ListNode fast = nHead;
ListNode slow = nHead;
while(n > 0){
fast = fast.next;
n--;
}
while(fast.next != null){
fast = fast.next;
slow = slow.next;
}
slow.next = slow.next.next;
return nHead.next;
}
面试题 02.07. 链表相交
题目链接:面试题 02.07. 链表相交 - 力扣(LeetCode)
解题思路:首先确定两个链表的长度差,先移动长链表的指针,移动到指定位置,然后长短链表共同往后移动指针,看何时两个指针指向相同结点,注意!不是判断两个指针指向结点的值是否相等,而是判断结点是否是同一个结点
public ListNode getIntersectionNode(ListNode headA, ListNode headB) {
ListNode p = headA;
ListNode q = headB;
int lenA = 0, lenB = 0;
while(p != null){
lenA++;
p = p.next;
}
while(q != null){
lenB++;
q = q.next;
}
if(lenB < lenA){
ListNode temp = headA;
headA = headB;
headB = temp;
}
p = headA;
q = headB;
int time = lenB > lenA ? lenB - lenA : lenA - lenB;
while(time > 0){
q = q.next;
time--;
}
while(p != q){
p = p.next;
q = q.next;
}
return p;
}
142.环形链表II
题目链接:142. 环形链表 II - 力扣(LeetCode)
解题思路:
经验教训:最初因为设置slow和fast都为头结点开始,为了避免if(fast == slow)直接触发,所以先在循环外执行了一次fast和slow指针的移动,然后再做while(fast != null && fast.next != null)的循环。但是实际上这样做是多余的。问题就在于没有理清先判断什么后判断什么?
实际上应该先判断是否有环,有环就一直循环下去知道找到答案,所以最外层循环就是保证fast指针的下移是合法的,也就是while(fast != null && fast.next != null)。
在有环的前提下,还需要考虑有一个或者两个结点的环是否需要单独考虑,这套逻辑是否有漏洞。根据代码模拟,发现有环情况下一个或者两个结点的环并不需要单独考虑。这是代码就已经形成了。
不要在开始之前对进行特殊处理,例如此题不要先在循环外进行一次fast和slow指针的移动,可能只是逻辑没有捋顺清楚。
每当有特殊情况需要处理时,都要问自己一下这样做真的有必要吗?正常情况是否已经涵盖了这种情况。
public ListNode detectCycle(ListNode head) {
// 快慢指针,数学解法
ListNode fast = head;
ListNode slow = head;
while(fast != null && fast.next != null){
fast = fast.next.next;
slow = slow.next;
if(fast == slow){
ListNode p = head;
while(p != slow){
p = p.next;
slow = slow.next;
}
return p;
}
}
return null;
}