diff --git a/include/s0142_linked_list_cycle.hpp b/include/s0142_linked_list_cycle.hpp new file mode 100644 index 0000000..d80835d --- /dev/null +++ b/include/s0142_linked_list_cycle.hpp @@ -0,0 +1,24 @@ +#ifndef S0142_LINKED_LIST_CYCLE_HPP +#define S0142_LINKED_LIST_CYCLE_HPP + +#include + +struct ListNode { + int val; + ListNode* next; + ListNode(int x) : val(x), next(nullptr) {} +}; + +struct IterResult { + ListNode *node; + bool meet; +}; + +class S0142 { + public: + std::unordered_map footprint; + IterResult iter(ListNode *fast, ListNode* slow, bool startUp); + ListNode *detectCycle(ListNode *head); +}; + +#endif diff --git a/notes/src/SUMMARY.md b/notes/src/SUMMARY.md index 3241eec..9e9f086 100644 --- a/notes/src/SUMMARY.md +++ b/notes/src/SUMMARY.md @@ -10,6 +10,7 @@ # 链表 - [总结](./linked-list.md) +- [环形链表](./linked_list_cycle.md) # 字符串 diff --git a/notes/src/linked-list.md b/notes/src/linked-list.md index 7f66395..d59e3d5 100644 --- a/notes/src/linked-list.md +++ b/notes/src/linked-list.md @@ -8,14 +8,18 @@ ```cpp void iter(ListNode *node) { + // 终止条件 if (node == nullptr) { return; } /* - your - condition + 从前往后遍历 */ iter(node->next); + /* + 从后往前遍历 + */ + return; } ``` diff --git a/notes/src/linked_list_cycle.md b/notes/src/linked_list_cycle.md new file mode 100644 index 0000000..7b0e803 --- /dev/null +++ b/notes/src/linked_list_cycle.md @@ -0,0 +1,40 @@ +# 环形链表 + +[Leetcode](https://leetcode.com/problems/linked-list-cycle-ii/) + +可以用回溯法解这道题。 + +首先递归遍历链表的一般结构如下: + +```cpp +void iter(ListNode *node) { + // 终止条件 + if (node == nullptr) { + return; + } + /* + 从前往后遍历 + */ + iter(node->next); + /* + 从后往前遍历 + */ + return; +} +``` + +而查找链表中是否有环的思路是快慢指针,如果相遇则说明有环。 + +所以我们可以这样: + +终止条件是快指针走到链表末尾或者快慢指针相遇。 + +从前往后遍历不需要做什么额外操作。 + +从后往前遍历的时候先把快指针的足迹记录到一个哈希表中,键值是节点地址,值是快指针经过的次数。 + +当出现以下两种情况的时候就说明找到了环的入口: + +1. footprint[fast] == 1 && footprint[fast->next] > 1 +2. footprint[fast->next] == 1 && footprint[fast->next->next] > 1 + diff --git a/src/s0142_linked_list_cycle.cpp b/src/s0142_linked_list_cycle.cpp new file mode 100644 index 0000000..0194f49 --- /dev/null +++ b/src/s0142_linked_list_cycle.cpp @@ -0,0 +1,70 @@ +#include "s0142_linked_list_cycle.hpp" + +IterResult S0142::iter(ListNode *fast, ListNode *slow, bool startUp) { + // 终止条件 + if (fast == nullptr || slow == nullptr) { + return {nullptr, false}; + } + if (fast->next == nullptr) { + return {nullptr, false}; + } + if (slow->next == slow) { + return {slow, true}; + } + // 当两指针相遇时同样达到终止条件 + if (fast == slow && !startUp) { + return {nullptr, true}; + } + // 递归 + IterResult result = iter(fast->next->next, slow->next, false); + // 从后往前遍历 + // 达到了终止条件,但是没有两个指针遇见过 + // 这说明没有环 + if (!result.meet) { + return {nullptr, false}; + } else { + // 有环的情况 + // 我们把 fast 和 fast-next 节点所经过的足迹全部记录下来 + // key 是所经过的节点 + // value 是所经过的次数 + if (footprint.count(fast) == 0) { + footprint[fast] = 1; + } else { + footprint[fast] += 1; + } + if (footprint.count(fast->next) == 0) { + footprint[fast->next] = 1; + } else { + footprint[fast->next] += 1; + } + // 什么情况下是环的入口呢? + // 1. footprint[fast] == 1 && footprint[fast->next] > 1 + // 2. footprint[fast->next] == 1 && footprint[fast->next->next] > 1 + if (footprint.count(fast) == 1 && footprint.count(fast->next) == 1) { + if (footprint[fast] == 1 && footprint[fast->next] > 1) { + return {fast->next, true}; + } + } + if (footprint.count(fast->next) == 1 && + footprint.count(fast->next->next) == 1) { + if (footprint[fast->next] == 1 && footprint[fast->next->next] > 1) { + return {fast->next->next, true}; + } + } + // 返回 + return result; + } +} + +ListNode *S0142::detectCycle(ListNode *head) { + if (head == nullptr) { + return nullptr; + } + if (head->next == nullptr) { + return nullptr; + } + // 递归函数无法处理所有节点都在环里的情况,所以在这里加一个哑节点 + ListNode dummy = ListNode(0); + dummy.next = head; + return iter(&dummy, &dummy, true).node; +} diff --git a/tests/s0142_linked_list_cycle.cpp b/tests/s0142_linked_list_cycle.cpp new file mode 100644 index 0000000..6f5c1c3 --- /dev/null +++ b/tests/s0142_linked_list_cycle.cpp @@ -0,0 +1,57 @@ +#include "s0142_linked_list_cycle.hpp" + +#include + +TEST(Problem142, Case1) { + ListNode node1 = ListNode(3); + ListNode node2 = ListNode(2); + ListNode node3 = ListNode(0); + ListNode node4 = ListNode(-4); + node1.next = &node2; + node2.next = &node3; + node3.next = &node4; + node4.next = &node2; + S0142 solution; + EXPECT_EQ(solution.detectCycle(&node1), &node2); +} + +TEST(Problem142, Case2) { + ListNode node1 = ListNode(3); + ListNode node2 = ListNode(2); + ListNode node3 = ListNode(0); + ListNode node4 = ListNode(-4); + node1.next = &node2; + node2.next = &node3; + node3.next = &node4; + node4.next = &node1; + S0142 solution; + EXPECT_EQ(solution.detectCycle(&node1), &node1); +} + +TEST(Problem142, Case3) { + ListNode node1 = ListNode(3); + ListNode node2 = ListNode(2); + ListNode node3 = ListNode(0); + ListNode node4 = ListNode(-4); + node1.next = &node2; + node2.next = &node3; + node3.next = &node4; + S0142 solution; + EXPECT_EQ(solution.detectCycle(&node1), nullptr); +} + +TEST(Problem142, Case4) { + ListNode node1 = ListNode(1); + ListNode node2 = ListNode(2); + node1.next = &node2; + node2.next = &node1; + S0142 solution; + EXPECT_EQ(solution.detectCycle(&node1), &node1); +} + +TEST(Problem142, Case5) { + ListNode node1 = ListNode(1); + node1.next = &node1; + S0142 solution; + EXPECT_EQ(solution.detectCycle(&node1), &node1); +}