引言
链表环问题是算法领域中的经典问题,它不仅涉及到链表操作,还可能涉及到双指针、哈希表等算法的应用。本文将对力扣上的一些经典链表环问题进行分析,并提供详细的C++代码实现。
环形链表检测
141. 环形链表
问题描述:给定一个链表,判断链表中是否有环。
问题分析:使用快慢指针法,慢指针每次移动一步,快指针每次移动两步,若存在环,则快指针会追上慢指针。
代码实现:
using namespace std;
class Solution {
public:
bool hasCycle(ListNode *head) {
ListNode *slow = head, *fast = head;
while (fast && fast->next) {
slow = slow->next; // 慢指针移动一步
fast = fast->next->next; // 快指针移动两步
if (slow == fast) return true; // 相遇说明存在环
}
return false; // 快指针到达链表尾部,说明无环
}
};
142. 环形链表 II
问题描述:给定一个链表,如果链表中有环,请返回环的入口。
问题分析:在快慢指针相遇后,一个指针重置为头节点,两个指针以相同速度移动,再次相遇时即为环的入口。
代码实现:
using namespace std;
class Solution {
public:
ListNode *detectCycle(ListNode *head) {
ListNode *slow = head, *fast = head;
// 使用快慢指针法找到环的相遇点
while (fast && fast->next) {
slow = slow->next;
fast = fast->next->next;
if (slow == fast) break;
}
if (!fast || !fast->next) return nullptr; // 无环
slow = head; // 重置慢指针到头节点
while (slow != fast) {
slow = slow->next;
fast = fast->next;
}
return slow; // 再次相遇的节点即为环的入口
}
};
160. 相交链表
问题描述:找出两个链表相交的起始节点。
问题分析:首先找到两个链表的长度,然后让较长链表的指针先移动相应步数,之后两个指针以相同速度移动,相遇时即为相交起始点。
代码实现:
using namespace std;
class Solution {
public:
ListNode *getIntersectionNode(ListNode *headA, ListNode *headB) {
ListNode *pA = headA, *pB = headB;
// 找到链表尾节点
while (pA) pA = pA->next;
while (pB) pB = pB->next;
// 如果pA或pB为nullptr,说明至少有一个链表为空,直接返回nullptr
if (!pA || !pB) return nullptr;
// 让pA和pB指向较长链表的头节点
if (pA == pB) pA = headA, pB = headB; // 两个链表长度相同
else if (pA->next) pA = headA; // pA较长
else pB = headB; // pB较长
// 以相同速度移动两个指针,相遇点即为相交起始节点
while (pA != pB) {
pA = pA->next;
pB = pB->next;
}
return pA;
}
};
725. 分隔链表
问题描述:给定一个链表和一个值 x ,请将链表中小于 x 的节点排在大于等于 x 的节点之前。
问题分析:使用快慢指针找到链表中第一个大于等于 x 的节点,然后重新连接链表。
代码实现:
using namespace std;
class Solution {
public:
ListNode* splitList(ListNode* head, int x) {
ListNode *leftTail = nullptr, *rightHead = nullptr, *rightTail = nullptr;
ListNode *current = head, *prev = nullptr;
// 遍历链表,分离小于x和大于等于x的节点
while (current) {
ListNode *next = current->next;
current->next = nullptr; // 断开连接
if (current->val < x) {
if (!leftTail) leftTail = current; // 第一个小于x的节点
if (prev) prev->next = current; // 连接到前一个小于x的节点
prev = current;
} else {
if (!rightHead) rightHead = current; // 第一个大于等于x的节点
if (rightTail) rightTail->next = current; // 连接到前一个大于等于x的节点
rightTail = current;
}
current = next;
}
// 连接左右两部分链表
if (prev) prev->next = rightHead;
if (rightTail) rightTail->next = nullptr; // 最后一个节点指向nullptr
return leftTail ? leftTail : rightHead;
}
};
21. 合并两个有序链表
问题描述:将两个有序链表合并为一个新的有序链表。
问题分析:递归或迭代地比较两个链表的节点,将较小的节点添加到新链表中。
代码实现:
using namespace std;
class Solution {
public:
ListNode* mergeTwoLists(ListNode* l1, ListNode* l2) {
if (!l1) return l2;
if (!l2) return l1;
// 使用哑节点简化边界条件处理
ListNode dummy(0);
ListNode *tail = &dummy;
while (l1 && l2) {
if (l1->val < l2->val) {
tail->next = l1;
l1 = l1->next;
} else {
tail->next = l2;
l2 = l2->next;
}
tail = tail->next;
}
// 连接剩余的节点
tail->next = l1 ? l1 : l2;
return dummy.next; // 返回新链表的头节点
}
};
876. 链表相似性
问题描述:如果两个链表在某个点相遇,返回相交节点的引用。
问题分析:使用双指针法,同时遍历两个链表,如果它们相遇或到达空节点,则找到了相交点。
代码实现:
using namespace std;
class Solution {
public:
bool isPalindrome(ListNode* head) {
if (!head || !head->next) return true;
// 快慢指针找到链表中点
ListNode *slow = head, *fast = head, *prev = nullptr;
while (fast && fast->next) {
fast = fast->next->next;
ListNode *temp = slow;
slow = slow->next;
slow->next = prev; // 反转前半部分链表
prev = temp;
}
// 如果fast不为空,说明链表长度为奇数,slow向前多走一步
if (fast) slow = slow->next;
// 比较前半部分和反转后的后半部分是否相同
while (prev) {
if (prev->val != slow->val) return false;
prev = prev->next;
slow = slow->next;
}
return true;
}
};
结语
通过上述问题的分析与实现,我们可以看到链表环问题的多样性和解决策略。无论是使用快慢指针法检测环的存在,还是通过双指针法找到相交链表的起始节点,理解算法背后的逻辑是解决问题的关键。
以上就是本次关于链表中的常见的环问题的求解,通过可以的练习,是能够很快掌握一门技术的。
给博主点个赞吧,后续更多知识的分享