两两交换链表中的节点
题目描述
给定一个链表,两两交换其中相邻的节点,并返回交换后的链表。
你不能只是单纯的改变节点内部的值,而是需要实际的进行节点交换。
解题思路
这道题可以画个图简单模拟一下就可以理清思路了,有几个注意点:
- 为了统一交换逻辑,可以新增一个哑结点,指向头结点,然后最终完成链表交换后,返回哑结点指向的下一个节点即可。
- 循环终止判断需要考虑节点是单数还是双数,需要注意先考虑双数的情况,即cur->next != nullptr,然后再考虑单数的情况,否测会引发程序异常
- 由于需要将哑节点指向节点从头节点指向第二个节点,因此需要通过临时变量记录头节点和第二个节点原先指向的节点位置(节点三),不然节点二就无法找到头结点的位置,头结点也找不到节点三的位置。
时空复杂度分析:
- 时间复杂度:O(n)
- 空间复杂度:O(1)
代码实现
测试地址:https://leetcode.cn/problems/swap-nodes-in-pairs/
class Solution {
public:
// 交换链表中每对相邻节点的函数
ListNode* swapPairs(ListNode* head) {
ListNode* dummyHead = new ListNode(0); // 创建一个哑节点,使其next指向链表头部,方便处理边界情况
ListNode* cur = dummyHead; // 使用cur节点来遍历链表,初始指向dummyHead
ListNode *tmp1, *tmp2; // 创建临时节点指针用于交换节点
dummyHead->next = head; // 将哑节点的next指向头节点
// 当当前节点的下一个节点和下下一个节点均不为nullptr时,执行循环
while (cur->next != nullptr && cur->next->next != nullptr) {
tmp1 = cur->next; // tmp1指向当前节点的下一个节点
tmp2 = cur->next->next->next; // tmp2指向当前节点的下下下个节点
// 交换操作
cur->next = cur->next->next; // 将cur的下一个节点指向tmp1的下一个节点(实现交换的一部分)
cur->next->next = tmp1; // 将交换后的新节点的next指向tmp1(完成交换)
tmp1->next = tmp2; // 调整tmp1的next,以指向tmp2
// 将cur移动到下一对要交换的节点的前一个节点
cur = cur->next->next;
}
return dummyHead->next; // 返回哑节点的next,即新的头节点
}
};
删除链表的倒数第N个节点
题目描述
给你一个链表,删除链表的倒数第 n
个结点,并且返回链表的头结点。
解题思路
总体思路:
-
因为我们需要删除倒数第n个节点,因此可以设定两个指针,让快指针先走n步,此时让慢指针跟随快指针进行移动,当快指针移动到空节点的时候,这时候慢指针恰好就能抵达倒数第n个位置了。
-
由于我们需要进行的是删除元素操作,实际上我们需要操作倒数第n-1位置上的元素,让其指向n+1位置的元素,因此slow指向的位置应该是倒数第n-1个元素,即快指针应该比慢指针先走n+1步。
时空复杂度分析:
- 时间复杂度: O(n)
- 空间复杂度: O(1)
代码实现
测试地址:https://leetcode.cn/problems/remove-nth-node-from-end-of-list/description/
/**
* 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 != nullptr) {
fast = fast->next;
}
//fast再提前走一步,因为需要让slow指向删除节点的上一个节点
fast = fast->next;
while (fast != nullptr) {
fast = fast->next;
slow = slow->next;
}
slow->next = slow->next->next;
return dummyHead->next;
}
};
链表相交## 题目描述
给你两个单链表的头节点 headA 和 headB ,请你找出并返回两个单链表相交的起始节点。如果两个链表没有交点,返回 null 。
图示两个链表在节点 c1 开始相交:
题目数据 保证 整个链式结构中不存在环。
注意,函数返回结果后,链表必须 保持其原始结构 。
示例 1:
示例 2:
示例 3:
解题思路
总体思路:
- 依次遍历两条链表,计算两条链表的长度差值:diff
- 判断两条链表最后一个节点的值是否相等,如果相等说明链表相交
- 让长度较长的链表先移动diff次,然后同时遍历两条链表,当链表元素相同时,返回当前元素为相交节点
代码实现
测试地址:https://leetcode.cn/problems/intersection-of-two-linked-lists/
class Solution {
public:
// 寻找两个单链表交叉点的函数
ListNode* getIntersectionNode(ListNode* h1, ListNode* h2) {
// 如果任意一个链表的头指针为空,则两链表不相交
if (h1 == nullptr || h2 == nullptr) {
return nullptr;
}
// 初始化两个指针用于遍历两个链表
ListNode *a = h1, *b = h2;
int diff = 0; // 记录两链表长度的差值
// 遍历第一个链表到最后一个节点,同时计算链表长度
while (a->next != nullptr) {
a = a->next;
diff++;
}
// 遍历第二个链表到最后一个节点,同时计算链表长度,并调整长度差值'diff'
while (b->next != nullptr) {
b = b->next;
diff--;
}
// 如果'a'和'b'没有指向相同的节点,意味着两链表不相交
if (a != b) {
return nullptr;
}
// 重置指针'a'和'b'到各自链表的头部
// 基于哪个链表更长来调整起始指针
if (diff >= 0) {
a = h1;
b = h2;
} else {
a = h2;
b = h1;
}
// 将较长链表的指针向前移动'diff'的长度,使两链表剩余长度相同
diff = abs(diff);
while (diff-- != 0) {
a = a->next;
}
// 同步移动两个指针,直到它们指向同一个节点,表示找到了交叉点
while (a != b) {
a = a->next;
b = b->next;
}
// 返回交叉节点
return a;
}
};
环形链表
题目描述
给定一个链表的头节点 head
,返回链表开始入环的第一个节点。 如果链表无环,则返回 null
。
如果链表中有某个节点,可以通过连续跟踪 next
指针再次到达,则链表中存在环。 为了表示给定链表中的环,评测系统内部使用整数 pos
来表示链表尾连接到链表中的位置(索引从 0 开始)。如果 pos
是 -1
,则在该链表中没有环。注意:pos
不作为参数进行传递,仅仅是为了标识链表的实际情况。
不允许修改 链表。
解题思路
- 快慢指针检测环:使用两个指针,一个慢指针每次移动一步,一个快指针每次移动两步。如果在某个时刻快指针和慢指针相遇,说明链表中存在环。
- 确定环的入口:当快慢指针相遇后,将快指针移回链表头部,然后快慢指针每次都移动一步。当它们再次相遇时,相遇的节点就是环的入口。
- 返回环的入口节点:如果链表中不存在环,则返回
NULL
;如果存在环,则返回环的入口节点。
代码实现
测试地址:https://leetcode.cn/problems/linked-list-cycle-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) {
// 检查链表是否为空或只有一两个节点,这种情况下不可能有环
if(head == nullptr || head->next == nullptr || head->next->next == nullptr){
return nullptr;
}
// 初始化快慢两个指针
ListNode* slow = head->next; // 慢指针每次移动一步
ListNode* fast = head->next->next; // 快指针每次移动两步
// 使用快慢指针检测环的存在
while(slow != fast){
// 如果快指针到达链表末尾,说明没有环
if(fast->next == nullptr || fast->next->next == nullptr){
return nullptr;
}
// 移动快慢指针
slow = slow->next;
fast = fast->next->next;
}
// 如果存在环,重置快指针到链表头部,慢指针保持在相遇点
fast = head;
// 两个指针每次移动一步,直到再次相遇,相遇点即为环的起始节点
while(slow != fast){
slow = slow->next;
fast = fast->next;
}
// 返回环的起始节点
return slow;
}
};