算法训练营Day4
24.两两交换链表中的节点,19.删除链表的倒数第N个节点,面试题 02.07. 链表相交,142.环形链表II
题目
24.两两交换链表中的节点
题目链接:https://leetcode.cn/problems/swap-nodes-in-pairs/description/
文章讲解:https://programmercarl.com/0024.%E4%B8%A4%E4%B8%A4%E4%BA%A4%E6%8D%A2%E9%93%BE%E8%A1%A8%E4%B8%AD%E7%9A%84%E8%8A%82%E7%82%B9.html
思路
链表题
- 双指针:从头开始,每次让cur指针指向位置的后两个节点交换位置,注意指针交换顺序
易错点,执行结束后需要更新cur,cur指向1节点,因为他是交换1、2节点之后后面的一个节点 - 递归:需要画图,否则容易让指针丢失
函数返回的是交换两个节点之后的头指针,递归结束的条件是只有一个节点或没有节点,交换完成后需要向后移动两个指针
//双指针
class Solution {
public:
ListNode* swapPairs(ListNode* head) {
ListNode *dummy = new ListNode(0, head);
ListNode *cur = dummy;
while(cur -> next != nullptr && cur -> next -> next != nullptr){
ListNode *node1 = cur -> next, *node2 = cur -> next -> next -> next;
cur -> next = node1 -> next;
node1 -> next -> next = node1;
node1 -> next = node2;
cur = node1; //更新cur为两个节点中后面那个节点
}
return dummy -> next;
}
};
// 递归
class Solution {
public:
ListNode* swapPairs(ListNode* head) {
if(head == nullptr || head -> next == nullptr) return head;
ListNode *newHead = head -> next; //需要用newHead存储
head -> next = swapPairs(head -> next -> next);
newHead -> next = head;
return newHead;
}
};
19.删除链表的倒数第N个节点
题目链接:https://leetcode.cn/problems/remove-nth-node-from-end-of-list/description/
文章讲解:https://programmercarl.com/0019.%E5%88%A0%E9%99%A4%E9%93%BE%E8%A1%A8%E7%9A%84%E5%80%92%E6%95%B0%E7%AC%ACN%E4%B8%AA%E8%8A%82%E7%82%B9.html
思路
- 双指针:快慢指针,快指针比慢指针快n个单位,当快指针为NULL时,慢指针所指就是倒数第n个节点;要删除倒数第n个节点,所以慢指针要指向前一个节点,所以慢指针从dummy开始,快指针从dummy->next开始,两个指针之间间隔n+1个单位,保证慢指针指向前一个节点
- 栈:运用栈,将所有节点指针压入栈,再弹出n个,栈顶即为要删除节点的前一个节点指针
代码
//双指针
class Solution {
public:
ListNode* removeNthFromEnd(ListNode* head, int n) {
ListNode* dummy = new ListNode(0, head);
ListNode *slow = dummy, *fast = dummy -> next; //初始满足间隔一个单位
// 题意保证n合理
while (n--) {
fast = fast -> next;
}
while (fast != nullptr) {
slow = slow -> next;
fast = fast -> next;
}
slow -> next = slow -> next -> next;
return dummy->next;
}
};
//栈
class Solution {
public:
ListNode* removeNthFromEnd(ListNode* head, int n) {
ListNode* dummy = new ListNode(0, head);
stack<ListNode*> stk;
ListNode* cur =
dummy; // 如果删除第一个节点,需要它的前一个节点,所以从dummy开始压入栈中
while (cur != nullptr) {
stk.push(cur);
cur = cur->next;
}
while (n--) {
stk.pop();
}
ListNode* pre = stk.top();
pre->next = pre->next->next;
return dummy -> next;
}
};
面试题 02.07. 链表相交
题目链接:https://leetcode.cn/problems/intersection-of-two-linked-lists-lcci/
文章讲解:
https://programmercarl.com/%E9%9D%A2%E8%AF%95%E9%A2%9802.07.%E9%93%BE%E8%A1%A8%E7%9B%B8%E4%BA%A4.html
思路
数值相同,不代表指针相同
- 模拟法:若两个链表有交点,则链表尾部应该对齐。求两个链表长度,在长的链表中将指针移动到与短的链表对齐的位置同时向后遍历,找到交点
- 双指针:一个很巧妙的方法,假设链表有相交部分且相交部分为c,链表A中不相交部分为a,链表B中不相交部分为b,则链表A为a+c,链表B为b+c;先遍历链表A再遍历链表B,a+c+b+c与先遍历链表B再遍历链表A,b+c+a+c一定会有最后的c相交,所以让一个指针先遍历A再遍历B,一个先遍历B再遍历A即可;
这里用temp1 == nullptr而不是temp1 -> next在于当两个链表没有交点,最后两个指针都会为空,这样也会退出循环且返回nullptr - 哈希表:看了题解才想到,将链表A中所有节点地址存入哈希表,遍历链表B,链表B中的第一个在哈希表中存在节点地址,即为相交的节点地址
代码
//模拟法
class Solution {
public:
ListNode* getIntersectionNode(ListNode* headA, ListNode* headB) {
int a = 0, b = 0;
ListNode *temp1 = headA, *temp2 = headB;
while (temp1 != nullptr) {
a++;
temp1 = temp1->next;
}
while (temp2 != nullptr) {
b++;
temp2 = temp2->next;
}
int k = abs(a - b);
temp1 = headA, temp2 = headB;
if (a > b)
while (k--)
temp1 = temp1->next;
else
while (k--)
temp2 = temp2->next;
while (temp1 != nullptr) {
if (temp1 == temp2)
return temp1;
else {
temp1 = temp1->next;
temp2 = temp2->next;
}
}
return NULL;
}
};
//双指针
class Solution {
public:
ListNode *getIntersectionNode(ListNode *headA, ListNode *headB) {
ListNode *temp1 = headA, *temp2 = headB;
while(temp1 != temp2){
temp1 = temp1 == nullptr? headB : temp1 -> next;
temp2 = temp2 == nullptr? headA : temp2 -> next;
}
return temp1;
}
};
//哈希表
class Solution {
public:
ListNode* getIntersectionNode(ListNode* headA, ListNode* headB) {
unordered_set<ListNode*> visited;
ListNode* temp = headA;
while (temp != NULL) {
visited.insert(temp);
temp = temp->next;
}
temp = headB;
while (temp != NULL) {
if (visited.count(temp) == 1)
return temp;
temp = temp -> next;
}
return NULL;
}
};
142.环形链表II
题目链接:https://leetcode.cn/problems/linked-list-cycle-ii/description/
文章讲解:https://programmercarl.com/0142.%E7%8E%AF%E5%BD%A2%E9%93%BE%E8%A1%A8II.html
思路
- 哈希表:有了上一题的经验,很快想到用哈希表存储,遍历链表,如果哈希表中已存在这个节点地址,则为入环的第一个节点
- 双指针:先讲结论,从头结点出发,fast指针每次移动两个节点,slow指针每次移动一个节点,如果 fast 和 slow指针在途中相遇 ,说明这个链表有环
从头结点出发一个指针,从相遇节点也出发一个指针,这两个指针每次只走一个节点, 那么当这两个指针相遇的时候就是环形入口的节点,讲解中证明快慢指针一定在慢指针进入环的第一圈相遇,这里不再赘述
代码
//哈希表
class Solution {
public:
ListNode *detectCycle(ListNode *head) {
unordered_set<ListNode *> visited;
ListNode *temp = head;
while(temp != nullptr){
if(visited.count(temp) != 0) return temp;
visited.insert(temp);
temp = temp -> next;
}
return nullptr;
}
};
//双指针
class Solution {
public:
ListNode *detectCycle(ListNode *head) {
ListNode *slow = head, *fast = head;
//前者保证head为NULL时退出循环
while(fast != nullptr && fast -> next != nullptr){
slow = slow -> next;
fast = fast -> next -> next;
if(slow == fast){
ListNode *temp = head;
while(temp != slow){
temp = temp -> next;
slow = slow -> next;
}
return temp;
}
}
return nullptr;
}
};