24.两两交换链表中的节点
题目链接/文章讲解/视频讲解: https://programmercarl.com/0024.%E4%B8%A4%E4%B8%A4%E4%BA%A4%E6%8D%A2%E9%93%BE%E8%A1%A8%E4%B8%AD%E7%9A%84%E8%8A%82%E7%82%B9.html
19.删除链表的倒数第N个节点
题目链接/文章讲解/视频讲解:https://programmercarl.com/0019.%E5%88%A0%E9%99%A4%E9%93%BE%E8%A1%A8%E7%9A%84%E5%80%92%E6%95%B0%E7%AC%ACN%E4%B8%AA%E8%8A%82%E7%82%B9.html
面试题 02.07. 链表相交
题目链接/文章讲解:https://programmercarl.com/%E9%9D%A2%E8%AF%95%E9%A2%9802.07.%E9%93%BE%E8%A1%A8%E7%9B%B8%E4%BA%A4.html
142.环形链表II
题目链接/文章讲解/视频讲解:https://programmercarl.com/0142.%E7%8E%AF%E5%BD%A2%E9%93%BE%E8%A1%A8II.html
24. 两两交换链表中的节点
看卡哥视频感悟:
- 要想操作节点x,必须有志向x-1,也就是前一个节点的指针
- 判断循环中止的条件比我优雅
我的做法
延续上一篇的做法,主要有两点:
- 指针不要怕多,不够就加
- 只有指针指着的位置是能随便调整箭头指向位置,动左右两侧相邻的节点的时候一定要慎重
- 先一条一条写,再找出最小循环单位作为循环体
- 边界条件一定要试
下面来找循环体。首先假设有无限多个节点(这样就不用考虑边界情况)
结点1、结点2改完之后应该是这样的
然后开启多指针大法,找循环体
然后就是验各种边界条件
验证下来发现有这些边界条件
但这个写的太不优雅了,其实究其本质说的就是卡哥的边界条件,即pre == null || pre.next == null
的情况下,循环结束
我的代码如下
public class LinkedList_24_SwapNodesinPairs {
public ListNode swapPairs(ListNode head) {
// 排除空链表
if (head == null) return null;
// 排除只有一个节点的链表
if (head.next == null) return head;
// 有>=2的节点的链表
// 创建头结点
ListNode dummy = new ListNode();
dummy.next = head;
// 创建指针
ListNode pre = dummy;
ListNode cur = pre.next;
ListNode after = cur.next;
ListNode last = after.next;
while (true) {
// 开始交换
pre.next = after;
after.next = cur;
cur.next = last;
// 开始推进
pre = pre.next.next;
cur = pre.next;
if (cur == null) break;
after = cur.next;
if (after == null) break;
last = after.next;
}
return dummy.next;
}
}
19. 删除链表的倒数第 N 个结点
说实话,看到这道题目脑子里第一反应就是双指针,我也不知道怎么解释思路,反正就归功多做题的感觉吧
确定了双指针的题目之后就是开始找规律
注意点:
- 要删除节点x,必须有着指向x-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 dummy = new ListNode();
dummy.next = head;
// 创建双指针
ListNode cur = dummy;
ListNode toDeletePreNode = dummy;
// 移动cur指针作为探路先锋
for (int i = 0; i < n; i++) {
cur = cur.next;
}
// 双指针整体开始移动到尾巴,当cur指向最后一个节点,toDeletePreNode就指向了倒数第n+1个节点
// 因为只有指向前一个节点才能删掉这个节点
while (cur.next != null) {
cur = cur.next;
toDeletePreNode = toDeletePreNode.next;
}
// 删除倒数第n个节点
toDeletePreNode.next = toDeletePreNode.next.next;
return dummy.next;
}
}
面试题 02.07. 链表相交
又是拿到题脑子里瞬间就知道了做法,我也不知道怎么写思路…
但是这道题目写的时候有几个注意点
这是正确的代码
public ListNode getIntersectionNode(ListNode headA, ListNode headB) {
// 排除空链表的情况
if (headA == null || headB == null) return null;
// 现在链表至少有一个节点
ListNode p1 = headA;
ListNode p2 = headB;
int lenA = 1;
int lenB = 1;
int diff = 0;
// 计算链表1的长度
while (p1 != null) {
p1 = p1.next;
lenA++;
}
// 计算链表2的长度
while (p2 != null) {
p2 = p2.next;
lenB++;
}
// p1、p2归位
p1 = headA;
p2 = headB;
// 计算哪个链表长,长的那个先动,保持在同一起跑线
if (lenA > lenB) {
diff = lenA - lenB;
for (int i = 0; i < diff; i++) {
p1 = p1.next;
}
} else {
diff = lenB - lenA;
for (int i = 0; i < diff; i++) {
p2 = p2.next;
}
}
// 此时p1、p2所在的链表后方的节点个数是相等的,且p1 != p2
// 开始同时遍历链表,指向相同的节点,则位相交的节点,若遍历结束正常退出循环,则说明不相交
while (p1 != null) {
if (p1 == p2) {
return p1;
}
p1 = p1.next;
p2 = p2.next;
}
return null;
}
我一开始写的时候犯了这几个错误:
- 计算链表长度的时候我的循环条件写的是
while (p1.next != null)
。 - 循环体中先判断,再改动。也就是先做不改变数据的操作,再做改变数据的操作。我一开始写的是
while (p1 != null) {
p1 = p1.next;
p2 = p2.next;
if (p1 == p2) {
return p1;
}
}
这与写的话只要有一条链表是只有一个节点,就会出错
142. 环形链表 II
没啥好说的,卡哥牛
这是我一开始的思路,写到最后就推不下去了
我觉得我的问题在于我钻牛角尖了,我想用数学的方法给它算出来,而不是结合计算机的方法给它试出来
启示就是不要小气,多设变量,找关系
public ListNode detectCycle(ListNode head) {
if (head == null) return null;
ListNode fast = head;
ListNode slow = head;
while (fast.next != null && fast.next.next != null) {
fast = fast.next.next;
slow = slow.next;
if (fast == slow) {
ListNode index1 = head;
ListNode index2 = fast;
while (index1 != index2) {
index1 = index1.next;
index2 = index2.next;
}
return index1;
}
}
return null;
}