0. 单向链表如何找到倒数n个节点
一种容易想到的方法是,我们首先从头节点开始对链表进行一次遍历,得到链表的长度 LL。随后我们再从头节点开始对链表进行一次遍历,当遍历到第 L-n+1L−n+1 个节点时,它就是我们需要删除的节点。
为了与题目中的 nn 保持一致,节点的编号从 11 开始,头节点为编号 11 的节点。
为了方便删除操作,我们可以从哑节点开始遍历 L-n+1L−n+1 个节点。当遍历到第 L-n+1L−n+1 个节点时,它的下一个节点就是我们需要删除的节点,这样我们只需要修改一次指针,就能完成删除操作。
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;
}
};
方法二:栈
思路与算法
我们也可以在遍历链表的同时将所有节点依次入栈。根据栈「先进后出」的原则,我们弹出栈的第 nn 个节点就是需要删除的节点,并且目前栈顶的节点就是待删除节点的前驱节点。这样一来,删除操作就变得十分方便了。
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;
}
};
方法三:双指针解法
class Solution {
public:
ListNode* removeNthFromEnd(ListNode* head, int n) {
ListNode* dummy = new ListNode(-1);
dummy->next = head;
ListNode* front = dummy;
ListNode* back = dummy;
for(int i = 0; i < n; i++){
front = front->next;
}
while(front->next != nullptr){
front = front->next;
back = back->next;
}
back->next = back->next->next;
return dummy->next;
}
};
1. 判断链表是否有环?
方法一:最容易想到的方法是遍历所有节点,每次遍历到一个节点时,判断该节点此前是否被访问过。
具体地,我们可以使用哈希表来存储所有已经访问过的节点。每次我们到达一个节点,如果该节点已经存在于哈希表中,则说明该链表是环形链表,否则就将该节点加入哈希表中。重复这一过程,直到我们遍历完整个链表即可
class Solution {
public:
bool hasCycle(ListNode *head) {
unordered_set<ListNode*> seen;
while (head != nullptr) {
if (seen.count(head)) {
return true;
}
seen.insert(head);
head = head->next;
}
return false;
}
};
方法二:快慢指针
class Solution {
public:
bool hasCycle(ListNode *head) {
if(head == nullptr || head->next == nullptr){
return false;
}
ListNode* fast = head->next;
ListNode* slow = head;
while(fast != slow){
if(fast == nullptr || fast->next == nullptr){
return false;
}
fast = fast->next->next;
slow = slow->next;
}
return true;
}
};
找出环的入口
方法一:
class Solution {
public:
ListNode *detectCycle(ListNode *head) {
if(head == nullptr || head->next == nullptr){
return nullptr;
}
ListNode* fast = head;
ListNode* slow = 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;
}
};
方法二:
一个非常直观的思路是:我们遍历链表中的每个节点,并将它记录下来;一旦遇到了此前遍历过的节点,就可以判定链表中存在环。借助哈希表可以很方便地实现。
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. 判断两个链表是否交叉
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;
}
};
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;
}
};
3. 链表反转
/**
* 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* reverseBetween(ListNode* head, int left, int right) {
//头节点可能也需要反转,故加入虚节点
ListNode* dummy = new ListNode(-1);
dummy->next = head;
ListNode* pre = dummy;
ListNode* cur = nullptr;
ListNode* next = nullptr;
for(int i = 0; i < left - 1; i++){
pre = pre->next;
}
cur = pre->next;
for(int i = 0; i < right-left; i++){
next = cur->next;
cur->next = next->next;
next->next = pre->next;
pre->next = next;
}
return dummy->next;
}
};
常规解法
将需要反转的部分拆分,反转完毕后再进行拼接
class Solution {
public:
ListNode* reverseBetween(ListNode* head, int left, int right) {
ListNode* dummy = new ListNode(-1);
dummy->next = head;
ListNode* pre = dummy;
for(int i = 0; i < left - 1; i++){
pre = pre->next;
}
ListNode* rightNode = pre;
for(int i = 0; i < right - left + 1; i++){
rightNode = rightNode->next;
}
ListNode* leftNode = pre->next;
ListNode* cur = rightNode->next;
pre->next = nullptr;
rightNode->next = nullptr;
reverse(leftNode);
pre->next = rightNode;
leftNode->next = cur;
return dummy->next;
}
private:
void reverse(ListNode* head){
ListNode* pre = nullptr;
ListNode* cur = head;
ListNode* next = nullptr;
while(cur != nullptr){
next = cur->next;
cur->next = pre;
pre = cur;
cur = next;
}
}
};