LeetCode 代码随想录跟练 Day4
24.两两交换链表中的节点
题目描述:
给你一个链表,两两交换其中相邻的节点,并返回交换后链表的头节点。你必须在不修改节点内部的值的情况下完成本题(即,只能进行节点交换)。
示例 1:
输入:head = [1,2,3,4]
输出:[2,1,4,3]
示例 2:
输入:head = []
输出:[]
示例 3:
输入:head = [1]
输出:[1]
首先考虑到统一对头节点特殊情况的判断,定义指向head的dummy节点。要实现当前节点A和下一个节点B的两两交换需完成:记录A节点的前节点pre,记录B节点的后节点tmp=B->next,交换节点指向即B指向A,然后pre指向B以及A指向tmp,如下图
由于在交换时需要获取到被交换节点的next,所以需额外确保B不为nullptr。当B为nullptr时,由于在循环的上一步中已执行指向当前A的操作,不需要再执行其他操作。代码如下:
class Solution {
public:
ListNode* swapPairs(ListNode* head) {
ListNode* dummy = new ListNode(0);
dummy->next = head;
ListNode* pre = dummy;
ListNode* cur = dummy->next;
while (cur && cur->next) {
ListNode* front = cur;
ListNode* back = cur->next;
cur = back->next;
pre->next = back;
back->next = front;
front->next = cur;
pre = front;
}
return dummy->next;
}
};
19.删除链表的倒数第N个节点
题目描述:
给你一个链表,删除链表的倒数第 n 个结点,并且返回链表的头结点。
示例 1:
输入:head = [1,2,3,4,5], n = 2
输出:[1,2,3,5]
示例 2:
输入:head = [1], n = 1
输出:[]
示例 3:
输入:head = [1,2], n = 1
输出:[1]
直观的思路是首先获取链表的节点个数len,删除链表的倒数第N个节点即删除从head处开始的第len-n个节点,定义两个指针pre和cur分别从dummy和head移动,cur在遍历步长结束时指向需要被删除的节点,而这里的pre用来记录cur的前节点用于删除。代码如下:
class Solution {
public:
ListNode* removeNthFromEnd(ListNode* head, int n) {
ListNode* dummy = new ListNode(0);
dummy->next = head;
ListNode* pre = dummy;
ListNode* cur = head;
int len = 0;
while (cur) {
cur = cur->next;
++len;
}
int stride = len - n;
cur = head;
while (stride-- > 0) {
pre = pre->next;
cur = cur->next;
}
pre->next = cur->next;
delete cur;
return dummy->next;
}
};
除此之外,也可以使用快慢指针的方式,令fast指针先从dummy出发前进n步,之后slow和fast同时移动直到fast移动到链表的末尾(fast && fast->next)。因为fast提前出发前进了n步,也就是说slow和fast之间的距离是n,所以当fast再次出发前进到尾部时slow指向的便是倒数第n+1个元素(因为fast所在位置是倒数第1个元素)。代码如下:
class Solution {
public:
ListNode* removeNthFromEnd(ListNode* head, int n) {
ListNode* dummy = new ListNode(0);
dummy->next = head;
ListNode* slow = dummy;
ListNode* fast = dummy;
while (fast && n--) {
fast = fast->next;
}
while (fast && fast->next) {
slow = slow->next;
fast = fast->next;
}
ListNode* tmp = slow->next;
slow->next = tmp->next;
delete tmp;
return dummy->next;
}
};
160.相交链表
题目描述:
给你两个单链表的头节点 headA 和 headB ,请你找出并返回两个单链表相交的起始节点。如果两个链表不存在相交节点,返回 null 。
图示两个链表在节点 c1 开始相交:
题目数据 保证 整个链式结构中不存在环。
示例:略
思路为分别获取到A和B的长度,元素更多的链表在遍历时提前出发,提前出发的步长为两链表长度的差值,这样可以确保可以同时到达公共交点处(若存在)。代码如下:
class Solution {
public:
ListNode *getIntersectionNode(ListNode *headA, ListNode *headB) {
ListNode* curA = headA;
ListNode* curB = headB;
int lenA = 0;
int lenB = 0;
while (curA) {
curA = curA->next;
++lenA;
}
while (curB) {
curB = curB->next;
++lenB;
}
curA = headA;
curB = headB;
int dif;
if (lenA > lenB) {
dif = lenA - lenB;
while (dif--) {
curA = curA->next;
}
} else {
dif = lenB - lenA;
while (dif--) {
curB = curB->next;
}
}
while (curA && curB) {
if (curA == curB) {
return curA;
}
curA = curA->next;
curB = curB->next;
}
return nullptr;
}
};
同样基于这种思路进行优化,目的是确保同时到达公共交点位置,A和B在同时出发到达尾部时继续另一个链表的头部出发。这样两者在同时完成第二次遍历时走过的长度均为lenA+lenB,且若公共交点存在则一定会同时到达。代码如下:
class Solution {
public:
ListNode *getIntersectionNode(ListNode *headA, ListNode *headB) {
if (!headA || !headB) return nullptr;
ListNode* curA = headA;
ListNode* curB = headB;
while (curA != curB) {
curA = curA ? curA->next : headB;
curB = curB ? curB->next : headA;
}
return curA;
}
};
142.环形链表
题目描述:
给定一个链表的头节点 head ,返回链表开始入环的第一个节点。 如果链表无环,则返回 null。
如果链表中有某个节点,可以通过连续跟踪 next 指针再次到达,则链表中存在环。 为了表示给定链表中的环,评测系统内部使用整数 pos 来表示链表尾连接到链表中的位置(索引从 0 开始)。如果 pos 是 -1,则在该链表中没有环。注意:pos 不作为参数进行传递,仅仅是为了标识链表的实际情况。
不允许修改链表。
示例 1:
输入:head = [3,2,0,-4], pos = 1
输出:返回索引为 1 的链表节点
解释:链表中有一个环,其尾部连接到第二个节点。
示例 2:
输入:head = [1,2], pos = 0
输出:返回索引为 0 的链表节点
解释:链表中有一个环,其尾部连接到第一个节点。
示例 3:
输入:head = [1], pos = -1
输出:返回 null
解释:链表中没有环。
若链表中有环则对链表进行遍历时循环永远不会退出(cur != nullptr),同时若定义快慢两指针分别以固定步长1和2在链表中移动,则一定会相交。因为在每次移动时fast在移动后总会比slow多前进一格(索引:[0, 0], [1, 2], [2, 4], [3, 6], …),也就是每步一格远离;同理在环中fast追赶slow时也是会以每步一格接近,(以1为最小单位)所以一定会相遇。
在这个前提下可以得到两指针相交的节点。按照图中标示进行命名:
slow到达相交节点时长度设为n,则
{
(
x
+
y
)
=
n
(1)
(
x
+
y
)
+
k
(
y
+
z
)
=
2
n
(
2
)
\begin{cases} (x + y) = n \quad \text{(1)} \\ (x + y) +k(y+z)=2n \quad(2) \end{cases}
{(x+y)=n(1)(x+y)+k(y+z)=2n(2)
可以拆分为
x
+
y
x+y
x+y两者共同走过的距离
n
n
n,以及
(
z
+
k
(
y
+
z
)
)
(z+k(y+z))
(z+k(y+z))表示fast以比slow每步多走一格在环中绕圈再次停到相交点的距离
n
n
n。
令slow指针从起点出发,到达环入口时所走的距离如下(公式1)
d
i
s
t
a
n
c
e
S
l
o
w
=
x
=
n
−
y
distanceSlow=x=n-y
distanceSlow=x=n−y令fast从相遇点出发,步长和slow相同为1前进,到达环入口时所走距离为(公式2)
d
i
s
t
a
n
c
e
F
a
s
t
=
z
+
h
(
y
+
z
)
=
h
′
(
y
+
z
)
−
y
=
n
−
y
distanceFast=z+h(y+z)=h'(y+z)-y=n-y
distanceFast=z+h(y+z)=h′(y+z)−y=n−y无论fast在环中再次转了几圈,两者下次的相遇点一定为环入口。代码如下:
class Solution {
public:
ListNode *detectCycle(ListNode *head) {
ListNode* slow = head;
ListNode* fast = head;
while (fast && fast->next) {
slow = slow->next;
fast = fast->next->next;
if (slow == fast) {
slow = head;
while (fast != slow) {
slow = slow->next;
fast = fast->next;
}
return fast;
}
}
return nullptr;
}
};