代码随想录算法训练营Day04|24. 两两交换链表中的节点、19. 删除链表的倒数第 N 个结点、02.07.链表相交、142. 环形链表 II
代码随想录算法训练营Day04|24. 两两交换链表中的节点、19. 删除链表的倒数第 N 个结点、02.07.链表相交、142. 环形链表 II
24. 两两交换链表中的节点
题目链接:24. 两两交换链表中的节点
首先题干要求相邻两两交换,这里首先涉及到的是对奇偶链表长度如何判断:
-
奇偶判断:链表长度的奇偶是否需要进行区分。如果区分的话:偶数正常两两交换即可;奇数则只考虑前
n-1
个节点的交换问题。 -
虚拟头结点:还是建议设置虚拟头结点,统一所有链表节点的操作。
本题主要是考察代码执行流程是否清晰,重点在于画图理清节点变换流程,流程如下:
按照图示步骤,我们需要记录两个临时变量(以图中的1、2节点为例):
- 设置变量
temp1
记录1的位置 - 设置变量
temp2
记录3的位置
不需要记录2的位置是因为,当执行第一个步骤cur->next = cur->next->next
时,变换后我们仍然可以通过cur->next
来访问2节点。如果步骤间的顺序打乱,可能会需要更多的临时变量,造成额外的存储开销。
19. 删除链表的倒数第 N 个结点
题目链接:19. 删除链表的倒数第 N 个结点
本题的难点在于如何确定倒数第n
个节点,因为在遍历到末尾节点之前,不清楚剩余节点的个数,很容易联想到快慢指针的方式。另外本题有要求给出的n
一定在有效链表长度范围内,不需要进行异常处理。
代码如下:
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* ListNode *next;
* ListNode() : val(0), next(nullptr) {}
* ListNode(int x) : val(x), next(nullptr) {}
* ListNode(int x, ListNode *next) : val(x), next(next) {}
* };
*/
class Solution {
public:
ListNode* removeNthFromEnd(ListNode* head, int n) {
ListNode* dummyHead = new ListNode(0);
dummyHead->next = head;
ListNode* fast = dummyHead;
ListNode* slow = dummyHead;
while (n--) {
fast = fast->next;
}
while (fast->next != nullptr) {
fast = fast->next;
slow = slow->next;
}
// 此时要删除的节点就是slow的下位节点
ListNode* temp = slow->next;
slow->next = slow->next->next;
delete temp;
return dummyHead->next;
}
};
02.07.链表相交
题目链接:面试题 02.07. 链表相交
本题题干的信息比较多:
- 链表必须保持其原始结构:也就是说明不能使用反转链表的方式进行判断
- 链表相交的理解:交点不是数值相等,而是指针相等。从首位相交节点后所有链表节点值都相等,但值相等的节点可能也不是相交节点。
- 需要有异常处理:如果两链表没有相交,则返回相交节点值为0
本题的关键在于对齐:如何让两个链表遍历指针分别到其末尾节点的链表长度相等。我们求出两个链表的长度,并求出两个链表长度的差值,然后让curA
移动到,和curB
末尾对齐的位置。此时我们就可以比较curA
和curB
是否相同,如果不相同,同时向后移动curA
和curB
:
-
如果遇到
curA == curB
,则找到交点。 -
否则循环退出返回空指针。
代码如下:
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* ListNode *next;
* ListNode(int x) : val(x), next(NULL) {}
* };
*/
class Solution {
public:
ListNode *getIntersectionNode(ListNode *headA, ListNode *headB) {
int lenA = 0, lenB = 0, diff;
ListNode* curA = headA;
ListNode* curB = headB;
while (curA != NULL) {
lenA++;
curA = curA->next;
}
while (curB != NULL) {
lenB++;
curB = curB->next;
}
//比较前重置链表长度较短的头指针位置
if (lenA - lenB >= 0) {
diff = lenA - lenB;
cout << diff << endl;
curA = headA;
while (diff--) {
curA = curA->next;
}
curB = headB;
}
else {
diff = lenB - lenA;
cout << diff << endl;
curB = headB;
while (diff--) {
curB = curB->next;
}
curA = headA;
}
while (curA != NULL && curB != NULL) {
if (curA == curB)
return curA;
curA = curA->next;
curB = curB->next;
}
return 0;
}
};
代码中需要注意的是,不确定A、B哪个链表是较长的,所以需要对lenA-lenB
进行判断,当把较长的链表移动的对齐位置时,记得重置因为遍历链表长度而移动的另一链表指针。
当然,也可以默认lenA为较长链表的指针,如果lenB链表长度更大,我们对其进行一个转换即可,这样可能更好地提高代码的可读性和整洁性。
142. 环形链表 II(⭐️)
题目链接:142. 环形链表 II
涉及数学公式的推导。具体可以参考卡哥的博客内容,写的已经非常详细和透彻了142.环形链表II题解思路。
简单来说,就是采用快慢指针的方式从头指针开始分别采用步长两个节点、一个节点去遍历链表,如果有环则**两个节点必然在环中某处相遇,而头指针跟相遇点距离链表进入环的起点是相同距离的。****
代码如下:
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* ListNode *next;
* ListNode(int x) : val(x), next(NULL) {}
* };
*/
class Solution {
public:
ListNode *detectCycle(ListNode *head) {
ListNode* fast = head;
ListNode* slow = head;
bool isCircle = false;
while (fast != NULL && fast->next != NULL && fast->next->next != NULL) {
fast = fast->next->next;
slow = slow->next;
if (fast == slow) {
isCircle = true;
break;
}
}
if (!isCircle)
return nullptr;
slow = head;
while(fast != slow) {
fast = fast->next;
slow = slow->next;
}
return fast;
}
};