一、环形链表 I
方法1:哈希表
struct hashTable {
struct ListNode* key;
UT_hash_handle hh;
};
struct hashTable* hashtable;
struct hashTable* find(struct ListNode* ikey) {
struct hashTable* tmp;
HASH_FIND_PTR(hashtable, &ikey, tmp);
return tmp;
}
void insert(struct ListNode* ikey) {
struct hashTable* tmp = malloc(sizeof(struct hashTable));
tmp->key = ikey;
HASH_ADD_PTR(hashtable, key, tmp);
}
bool hasCycle(struct ListNode* head) {
hashtable = NULL;
while (head != NULL) {
if (find(head) != NULL) {
return true;
}
insert(head);
head = head->next;
}
return false;
}
代码解释
(1) HASH_FIND_PTR(hashtable, &ikey, tmp);
在这段代码中,hashtable 是哈希表的头指针,&ikey 是要查找的键,它是一个指向链表节点指针的指针的地址,tmp 是查找结果的输出参数,它是一个指向哈希表项的指针。因此,HASH_FIND_PTR(hashtable, &ikey, tmp) 的作用是在哈希表中查找指定键的哈希表项,并将结果存储在 tmp 中。如果找到了,则返回该哈希表项;否则返回 NULL。
(2)HASH_ADD_PTR(hashtable, key, tmp);
在这段代码中,hashtable 是哈希表的头指针,key 是哈希表项中表示键的字段,它是一个指向链表节点指针的指针,tmp 是要添加的键值对,它是一个指向哈希表项的指针。因此,HASH_ADD_PTR(hashtable, key, tmp) 的作用是将一个键值对添加到哈希表中,其中键是 tmp->key,即链表节点的指针,值是 tmp,即哈希表项的指针,同时更新哈希表的头指针 hashtable。
C++
class Solution {
public:
bool hasCycle(ListNode *head) {
//定义了一个 unordered_set<ListNode*> seen,
//表示存储已经遍历过的链表节点的集合。
unordered_set<ListNode*> seen;
while (head != nullptr) {
if (seen.count(head)) {
return true;
}
seen.insert(head);
head = head->next;
}
return false;
}
};
方法2:快慢指针
C
bool hasCycle(struct ListNode* head) {
if (head == NULL || head->next == NULL) {
return false;
}
struct ListNode* slow = head;
struct ListNode* fast = head->next;
while (slow != fast) {
//检测快指针 fast 是否到达了链表的末尾。如果 fast 指针到达了末尾,
//即 fast 指向空节点或者 fast 的下一个节点为空节点,则说明链表没有环,
//返回 false。
if (fast == NULL || fast->next == NULL) {
return false;
}
slow = slow->next;
fast = fast->next->next;
}
return true;
}
C++
class Solution {
public:
bool hasCycle(ListNode* head) {
if (head == nullptr || head->next == nullptr) {
return false;
}
ListNode* slow = head;
ListNode* fast = head->next;
while (slow != fast) {
if (fast == nullptr || fast->next == nullptr) {
return false;
}
slow = slow->next;
fast = fast->next->next;
}
return true;
}
};
二、环形链表 II
方法1:哈希表
C
struct hashTable {
struct ListNode* key;
UT_hash_handle hh;
};
struct hashTable* hashtable;
struct hashTable* find(struct ListNode* ikey) {
struct hashTable* tmp;
HASH_FIND_PTR(hashtable, &ikey, tmp);
return tmp;
}
void insert(struct ListNode* ikey) {
struct hashTable* tmp = malloc(sizeof(struct hashTable));
tmp->key = ikey;
HASH_ADD_PTR(hashtable, key, tmp);
}
struct ListNode* detectCycle(struct ListNode* head) {
hashtable = NULL;
while (head != NULL) {
if (find(head) != NULL) {
return head;
}
insert(head);
head = head->next;
}
return false;
}
C++
class Solution {
public:
ListNode *detectCycle(ListNode *head) {
unordered_set<ListNode *> visited;
while (head != nullptr) {
if (visited.count(head)) {
return head;
}
visited.insert(head);
head = head->next;
}
return nullptr;
}
};
方法2:快慢指针
C
struct ListNode* detectCycle(struct ListNode* head) {
struct ListNode *slow = head, *fast = head;
while (fast != NULL) {
slow = slow->next;
if (fast->next == NULL) {
return NULL;
}
fast = fast->next->next;
if (fast == slow) {
struct ListNode* ptr = head;
while (ptr != slow) {
ptr = ptr->next;
slow = slow->next;
}
return ptr;
}
}
return NULL;
}
C++
class Solution {
public:
ListNode *detectCycle(ListNode *head) {
ListNode *slow = head, *fast = head;
while (fast != nullptr) {
slow = slow->next;
if (fast->next == nullptr) {
return nullptr;
}
fast = fast->next->next;
if (fast == slow) {
ListNode *ptr = head;
while (ptr != slow) {
ptr = ptr->next;
slow = slow->next;
}
return ptr;
}
}
return nullptr;
}
};
三、相交链表
方法1:哈希集合
C
struct HashTable {
struct ListNode *key;
UT_hash_handle hh;
};
struct ListNode *getIntersectionNode(struct ListNode *headA, struct ListNode *headB) {
struct HashTable *hashTable = NULL;
struct ListNode *temp = headA;
while (temp != NULL) {
struct HashTable *tmp;
HASH_FIND(hh, hashTable, &temp, sizeof(struct HashTable *), tmp);
if (tmp == NULL) {
tmp = malloc(sizeof(struct HashTable));
tmp->key = temp;
HASH_ADD(hh, hashTable, key, sizeof(struct HashTable *), tmp);
}
temp = temp->next;
}
temp = headB;
while (temp != NULL) {
struct HashTable *tmp;
HASH_FIND(hh, hashTable, &temp, sizeof(struct HashTable *), tmp);
if (tmp != NULL) {
return temp;
}
temp = temp->next;
}
return NULL;
}
代码解释:
(1)HASH_FIND(hh, hashTable, &temp, sizeof(struct HashTable *), tmp);
HASH_FIND(hh, hashTable, &temp, sizeof(struct HashTable *), tmp); 的作用是在哈希表 hashTable 中查找是否存在指针 temp 所指向的 ListNode 节点,如果找到了就将指向 HashTable 结构体的指针赋值给 tmp,否则将 tmp 赋值为 NULL。
(2)HASH_ADD(hh, hashTable, key, sizeof(struct HashTable *), tmp);
HASH_ADD(hh, hashTable, key, sizeof(struct HashTable *), tmp); 的作用是将指针 tmp 所指向的 HashTable 结构体添加到哈希表 hashTable 中。具体来说,它将 tmp 关联到 key 成员(即指向 ListNode 的指针),并将 tmp 添加到哈希表中。
C++
class Solution {
public:
ListNode *getIntersectionNode(ListNode *headA, ListNode *headB) {
unordered_set<ListNode *> visited;
ListNode *temp = headA;
while (temp != nullptr) {
visited.insert(temp);
temp = temp->next;
}
temp = headB;
while (temp != nullptr) {
if (visited.count(temp)) {
return temp;
}
temp = temp->next;
}
return nullptr;
}
};
方法2:双指针
C
struct ListNode *getIntersectionNode(struct ListNode *headA, struct ListNode *headB) {
if (headA == NULL || headB == NULL) {
return NULL;
}
struct ListNode *pA = headA, *pB = headB;
while (pA != pB) {
pA = pA == NULL ? headB : pA->next;
pB = pB == NULL ? headA : pB->next;
}
return pA;
}
C++
class Solution {
public:
ListNode *getIntersectionNode(ListNode *headA, ListNode *headB) {
if (headA == nullptr || headB == nullptr) {
return nullptr;
}
ListNode *pA = headA, *pB = headB;
while (pA != pB) {
pA = pA == nullptr ? headB : pA->next;
pB = pB == nullptr ? headA : pB->next;
}
return pA;
}
};
四、删除链表的倒数第N个节点
方法1:计算链表长度
C
int getLength(struct ListNode* head) {
int length = 0;
while (head) {
++length;
head = head->next;
}
return length;
}
struct ListNode* removeNthFromEnd(struct ListNode* head, int n) {
struct ListNode* dummy = malloc(sizeof(struct ListNode));
dummy->val = 0, dummy->next = head;
int length = getLength(head);
struct ListNode* cur = dummy;
for (int i = 1; i < length - n + 1; ++i) {
cur = cur->next;
}
cur->next = cur->next->next;
struct ListNode* ans = dummy->next;
free(dummy);
return ans;
}
C++
class Solution {
public:
int getLength(ListNode* head) {
int length = 0;
while (head) {
++length;
head = head->next;
}
return length;
}
ListNode* removeNthFromEnd(ListNode* head, int n) {
ListNode* dummy = new ListNode(0, head);
int length = getLength(head);
ListNode* cur = dummy;
for (int i = 1; i < length - n + 1; ++i) {
cur = cur->next;
}
cur->next = cur->next->next;
ListNode* ans = dummy->next;
delete dummy;
return ans;
}
};
代码解释:
(1)ListNode* dummy = new ListNode(0, head);
在C++中,使用new关键字可以动态地在堆上分配内存,返回指向该内存的指针。ListNode(0, head)是调用ListNode类的构造函数,创建一个新的ListNode对象,并将0和head作为参数传递给构造函数。因为该构造函数接收两个参数,所以使用了括号括起来的两个参数。
这段代码的作用是创建一个虚拟头节点,将其值初始化为0,next指针指向head,这样可以方便处理删除头节点的情况。因为该虚拟头节点是动态分配的,所以在函数结束时需要调用delete关键字释放动态分配的内存。
方法2:栈
C
struct Stack {
struct ListNode* val;
struct Stack* next;
};
struct ListNode* removeNthFromEnd(struct ListNode* head, int n) {
struct ListNode* dummy = malloc(sizeof(struct ListNode));
dummy->val = 0, dummy->next = head;
struct Stack* stk = NULL;
struct ListNode* cur = dummy;
while (cur) {
struct Stack* tmp = malloc(sizeof(struct Stack));
tmp->val = cur, tmp->next = stk;
stk = tmp;
cur = cur->next;
}
for (int i = 0; i < n; ++i) {
struct Stack* tmp = stk->next;
free(stk);
stk = tmp;
}
struct ListNode* prev = stk->val;
prev->next = prev->next->next;
struct ListNode* ans = dummy->next;
free(dummy);
return ans;
}
代码解释:
(1)tmp->val = cur, tmp->next = stk;stk = tmp;
第一行代码将 tmp 的 val 字段设置为 cur 的当前值,即将当前节点压入栈中。第二行代码将 tmp 的 next 字段设置为栈的当前顶部节点 stk 的地址,表示将新节点的下一个节点设置为当前栈顶节点。最后,将 stk 的值更新为 tmp 的地址,即将新节点 tmp 设置为栈的新顶部节点。这样,新节点就被成功压入了栈中。
C++
class Solution {
public:
ListNode* removeNthFromEnd(ListNode* head, int n) {
ListNode* dummy = new ListNode(0, head);
stack<ListNode*> stk;
ListNode* cur = dummy;
while (cur) {
stk.push(cur);
cur = cur->next;
}
for (int i = 0; i < n; ++i) {
stk.pop();
}
ListNode* prev = stk.top();
prev->next = prev->next->next;
ListNode* ans = dummy->next;
delete dummy;
return ans;
}
};
代码解释
(1)stk.push(cur);
这行代码的作用是将当前节点 cur 压入栈中。具体来说,代码使用一个 stack 容器来存储链表中的每个节点,每当遍历到一个节点时,就将其压入栈中,以便后续在栈中访问该节点。由于栈是一种后进先出(LIFO)的数据结构,因此在遍历完整个链表后,栈中存储的节点顺序就是链表中节点的逆序。
(2)stk.pop();
这行代码的作用是从栈中弹出一个节点,即将栈顶的节点删除。具体来说,代码使用一个 for 循环从栈中弹出前 n 个节点,即跳过倒数第 k 个节点。由于栈是一种后进先出(LIFO)的数据结构,因此每次 stk.pop() 操作都会弹出栈顶的节点,直到循环结束时,栈中存储的节点就是链表中从头节点开始到倒数第 k+1 个节点的所有节点。
(3)stk.top();
这行代码的作用是获取栈顶节点的指针,即获取栈中最近压入的节点。具体来说,代码使用一个 stack 容器来存储链表中的每个节点,每当遍历到一个节点时,就将其压入栈中,以便后续在栈中访问该节点。
由于栈是一种后进先出(LIFO)的数据结构,因此栈顶节点就是最近压入栈中的节点,可以使用 stk.top() 来获取栈顶节点的指针。在本实现中,栈中存储的是指向链表节点的指针,因此 stk.top() 返回的是一个指向栈顶节点的指针。
方法3:双指针
C
struct ListNode* removeNthFromEnd(struct ListNode* head, int n) {
struct ListNode* dummy = malloc(sizeof(struct ListNode));
dummy->val = 0, dummy->next = head;
struct ListNode* first = head;
struct ListNode* second = dummy;
for (int i = 0; i < n; ++i) {
first = first->next;
}
while (first) {
first = first->next;
second = second->next;
}
second->next = second->next->next;
struct ListNode* ans = dummy->next;
free(dummy);
return ans;
}
C++
class Solution {
public:
ListNode* removeNthFromEnd(ListNode* head, int n) {
ListNode* dummy = new ListNode(0, head);
ListNode* first = head;
ListNode* second = dummy;
for (int i = 0; i < n; ++i) {
first = first->next;
}
while (first) {
first = first->next;
second = second->next;
}
second->next = second->next->next;
ListNode* ans = dummy->next;
delete dummy;
return ans;
}
};