链表处理的细节较多,故在此大多给出具体代码。
一、修改链表
203. 移除链表中所有等于给定值的节点
细节注意:设置 dummy head 简化过程,并注意不要访问空指针。
// iteration
class Solution {
public:
ListNode* removeElements(ListNode* head, const int val) {
ListNode* dummyHead = new ListNode(0, head);
ListNode* cur = dummyHead;
while (nullptr != cur->next) {
if (val == cur->next->val) {
ListNode *next = cur->next;
cur->next = next->next;
delete next;
} else {
cur = cur->next;
}
}
return dummyHead->next;
}
};
// recursion
class Solution {
public:
ListNode* removeElements(ListNode* head, const int val) {
if (head == nullptr) {
return head;
}
head->next = removeElements(head->next, val);
if (val == head->val) {
ListNode *prev = head;
head = head->next;
delete prev;
}
return head;
}
};
707. 设计链表
注意单链表和双向链表的实现区别,添加和删除节点时需要前序节点的信息。
206. 反转链表
// iteration
class Solution {
public:
ListNode* reverseList(ListNode* head) {
ListNode* curr = head, * prev = nullptr;
while (nullptr != curr) {
ListNode *next = curr->next;
curr->next = prev;
prev = curr;
curr = next;
}
return prev;
}
};
// recursion
class Solution {
public:
ListNode* reverseList(ListNode* head) {
if (nullptr == head || nullptr == head->next)
return head;
ListNode *rHead = reverseList(head->next);
head->next->next = head;
head->next = nullptr;
return rHead;
}
};
24. 两两交换链表中的节点
// iteration
class Solution {
public:
ListNode* swapPairs(ListNode* head) {
if (head == nullptr || head->next == nullptr) {
return head;
}
// Firstly, recursively process the part after newHead, then swap head and newHead
ListNode* newHead = head->next;
head->next = swapPairs(newHead->next);
newHead->next = head;
return newHead;
}
};
// recursion
class Solution {
public:
ListNode* swapPairs(ListNode* head) {
ListNode* dummyHead = new ListNode(0);
dummyHead->next = head;
ListNode* temp = dummyHead;
// At the beginning of each loop, temp points to the predecessor node of the two nodes to be swapped
while (temp->next != nullptr && temp->next->next != nullptr) {
ListNode* node1 = temp->next;
ListNode* node2 = temp->next->next;
temp->next = node2;
node1->next = node2->next;
node2->next = node1;
temp = node1;
}
ListNode* result = dummyHead->next;
delete dummyHead;
return result;
}
};
19. 删除链表的倒数第 n 个节点
双指针法,让靠前的指针领先靠后的指针 n + 1 个位置,当靠前的指针到达 NULL 时,删除靠后的指针的后继节点即可。
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;
}
};
二、获取链表属性
面试题 02.07. 判定链表是否有交点并返回交点
双指针法,假定一个链表的尾端连接在另一个链表的首端,则两个指针最终会在交点或同时为 nullptr.
class Solution {
public:
ListNode *getIntersectionNode(ListNode *headA, ListNode *headB) {
ListNode *pA = headA, *pB = headB;
while (pA != pB) {
pA = pA == nullptr ? headB : pA->next;
pB = pB == nullptr ? headA : pB->next;
}
return pA;
}
};
141. 判断单链表是否有环
见下一题。
142. 判断单链表是否有环并返回入环节点
快慢指针法,让快慢指针同时从 head 开始,若二者能相遇,则说明有环。判定有环后,令一个指针从 head 开始前进,另一个节点从相遇点开始前进,可证明若二者相遇,则相遇点为入环点。
证明:假设 head 到入环点要走 x 步,入环点到相遇点要走 y 步,相遇点到入环点要走 z 步。则易知快慢指针相遇时,慢指针走了 x + y 步,快指针走了 x + 2 * y + z 步,故有 2 * (x + y) = x + 2 * y + z,即 x = z.
class Solution {
public:
ListNode *detectCycle(ListNode *head) {
ListNode* fast = head;
ListNode* slow = head;
while(fast != NULL && fast->next != NULL) {
slow = slow->next;
fast = fast->next->next;
if (slow == fast) {
ListNode* index1 = fast;
ListNode* index2 = head;
while (index1 != index2) {
index1 = index1->next;
index2 = index2->next;
}
return index2;
}
}
return NULL;
}
};