算法记录 Day 4
24. 两两交换链表中的节点
解题思路
1. 迭代
class Solution {
public ListNode swapPairs(ListNode head) {
ListNode dumyhead = new ListNode(-1); // 设置一个虚拟头结点
dumyhead.next = head; // 将虚拟头结点指向head,这样方面后面做删除操作
ListNode cur = dumyhead;
ListNode temp; // 临时节点,保存两个节点后面的节点
ListNode firstnode; // 临时节点,保存两个节点之中的第一个节点
ListNode secondnode; // 临时节点,保存两个节点之中的第二个节点
while (cur.next != null && cur.next.next != null) {
temp = cur.next.next.next;
firstnode = cur.next;
secondnode = cur.next.next;
cur.next = secondnode; // 步骤一
secondnode.next = firstnode; // 步骤二
firstnode.next = temp; // 步骤三
cur = firstnode; // cur移动,准备下一轮交换
}
return dumyhead.next;
}
}
难点
- 终止条件,分奇数偶数,刚好是
cur.next != null && cur.next.next != null
- 想清楚3个步骤,以及需要提前保存的指针值
- 执行完一次操作后,curr记得向后移动两格。
2. 递归
class Solution {
// 定义:输入以 head 开头的单链表,将这个单链表中的每两个元素翻转,
// 返回翻转后的链表头结点
public ListNode swapPairs(ListNode head) {
if (head == null || head.next == null) {
return head;
}
ListNode first = head;
ListNode second = head.next;
ListNode others = head.next.next;
// 先把前两个元素翻转
second.next = first;
// 利用递归定义,将剩下的链表节点两两翻转,接到后面
first.next = swapPairs(others);
// 现在整个链表都成功翻转了,返回新的头结点
return second;
}
}
难点
使用递归来解决该题,主要就是递归的三部曲:
- 找终止条件:本题终止条件很明显,当递归到链表为空或者链表只剩一个元素的时候,没得交换了,自然就终止了。
- 找返回值:返回给上一层递归的值应该是已经交换完成后的子链表。
- 单次的过程:因为递归是重复做一样的事情,所以从宏观上考虑,只用考虑某一步是怎么完成的。我们假设待交换的俩节点分别为first 和 second,second应该接受上一级返回的子链表(参考第2步)。
19. 删除链表倒数第n个节点
解题思路
双指针的经典应用,如果要删除倒数第n个节点,让fast移动n步,然后让fast和slow同时移动,直到fast指向链表末尾。删掉slow所指向的节点就可以了。
class Solution {
public ListNode removeNthFromEnd(ListNode head, int n) {
ListNode dummy = new ListNode(-1);
dummy.next = head;
ListNode fast;
ListNode slow;
fast = dummy;
slow = dummy;
while( n-->0 && fast!=null){
fast = fast.next;
}
fast = fast.next;
while(fast != null){
fast = fast.next;
slow = slow.next;
}
slow.next = slow.next.next;
return dummy.next;
}
}
难点
fast
首先走n + 1步 ,为什么是n+1呢,因为只有这样同时移动的时候slow才能指向删除节点的上一个节点(方便做删除操作。
160. 链表相交
解题思路
public class Solution {
public ListNode getIntersectionNode(ListNode headA, ListNode headB) {
// p1 指向 A 链表头结点,p2 指向 B 链表头结点
ListNode p1 = headA, p2 = headB;
while(p1 != p2){
// p1 走一步,如果走到 A 链表末尾,转到 B 链表
if(p1 == null) {
p1 = headB;
}
else {
p1 = p1.next;
}
// p2 走一步,如果走到 B 链表末尾,转到 A 链表
if (p2 == null) {
p2 = headA;
}
else{
p2 = p2.next;
}
}
return p1;
}
}
难点
如果用两个指针 p1 和 p2 分别在两条链表上前进,并不能同时走到公共节点,也就无法得到相交节点 c1。
解决这个问题的关键是,通过某些方式,让 p1 和 p2 能够同时到达相交节点 c1。
如果用两个指针 p1 和 p2 分别在两条链表上前进,我们可以让 p1 遍历完链表 A 之后开始遍历链表 B,让 p2 遍历完链表 B 之后开始遍历链表 A,这样相当于**「逻辑上」**两条链表接在了一起。
如果这样进行拼接,就可以让 p1 和 p2 同时进入公共部分,也就是同时到达相交节点 c1。
14. 环形列表2
解题思路
public class Solution {
public ListNode detectCycle(ListNode head) {
ListNode fast;
ListNode slow;
ListNode index1;
ListNode index2;
fast = head;
slow = head;
while( fast != null && fast.next != null){
fast = fast.next.next;
slow = slow.next;
// 找到相遇点
if(fast == slow){
index1 = fast;
index2 = head;
// 相遇点到入口的位置 = 头结点到入口的位置
while(index1 !=index2){
index1 = index1.next;
index2 = index2.next;
}
return index1;
}
}
return null;
}
}
难点
主要是一些数学上的难点
fast
相对于slow
以一倍速移动,所以一定会相遇,并且一定在第一圈相遇。(详细可见代码随想录视频)- 相遇点到入口的距离 = 头结点到入口的距离。
链表总结
虚拟头结点
- 什么时候需要用虚拟头结点?
-当你需要创造一条新链表的时候,可以使用虚拟头结点简化边界情况的处理.