今天跟着Carl哥刷算法题的第4天,链表题目的第二部分,还是要多练习。
今日任务
● 24. 两两交换链表中的节点
● 19.删除链表的倒数第N个节点
● 面试题 02.07. 链表相交
● 142.环形链表II
● 总结
题目一: 24. 两两交换链表中的节点
思路: 用虚拟头结点,这样会方便很多。 本题链表操作就比较复杂了,建议大家先看视频,视频里我讲解了注意事项,为什么需要temp保存临时节点。
Leetcode 题目 :24. 两两交换链表中的节点
题目链接/文章讲解/视频讲解
此题目中需要注意的点:
(1)while什么时候结束,偶数时是cur -> next == NULL结束,奇数时是cur -> next -> next ==NULL 结束;
(2)虽然是虚拟头节点,但是在写程序的时候还是要按真正的新节点创建的。
/**
* 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* swapPairs(ListNode* head) {
ListNode *dummyNode = new ListNode(0);
dummyNode -> next = head;
ListNode *cur = dummyNode;
while(cur -> next != NULL && cur->next->next != NULL)
{
ListNode *temp, *temp1;
temp = cur -> next;
temp1 = cur -> next -> next -> next;
cur -> next = cur -> next -> next;
cur -> next -> next = temp;
cur -> next -> next -> next = temp1;
cur = temp;
}
return dummyNode->next;
}
};
题目二:19.删除链表的倒数第N个节点
思路: 双指针的操作,要注意,删除第N个节点,那么我们当前遍历的指针一定要指向 第N个节点的前一个节点,建议先看视频。
Leetcode题目: 19. 删除链表中的倒数第N个节点
题目链接/文章讲解/视频讲解:
此题目中的双指针方法太巧妙了,根本想不到,因此一开始采用的是比较暴力的解法,先遍历整个链表,判断链表的个数,然后按照正着删除的方法来就可以了。
暴力解法:
/**
* 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* removeNthFromEnd(ListNode* head, int n) {
// 暴力解法,先遍历出链表中有多少元素,然后倒置删除元素即可
ListNode *dummyNode = new ListNode(0);
dummyNode -> next = head;
ListNode *cur = dummyNode;
int count = 0, real_order = 0;
// 遍历有多少个元素
while(cur -> next != NULL){
cur = cur -> next;
count++;
}
real_order = count - n + 1;
cur = dummyNode;
for(int i = 1; i < real_order; i++){
cur = cur -> next;
}
cur -> next = cur -> next -> next;
return dummyNode -> next;
}
};
双指针解法:
/**
* 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* removeNthFromEnd(ListNode* head, int n) {
// 双指针解法:让fast和slow指针中间隔N个数值,然后快指针到链表最后的NULL,然后再转化一下就是从后面数第几个了
ListNode *dummyNode = new ListNode(0);
dummyNode -> next = head;
ListNode *fast = dummyNode;
ListNode *slow = dummyNode;
// 先让快指针移动,与满指针差 n+1 个位置
while(n-- && fast -> next != NULL){
fast = fast -> next;
}
fast = fast -> next;
while(fast != NULL){
fast = fast -> next;
slow = slow -> next;
}
slow -> next = slow->next->next;
return dummyNode -> next;
}
};
题目三:面试题 02.07. 链表相交
题目链接/文章讲解
Leetcode题目:面试题 02.07. 链表相交
这个题一开始写错了,注意不是值相同,而是指针相同,如果指针相同了,那他们指的后面的所有的东西都会相同。
代码如下,整体来说思路比较简单,就是要对齐,为了保证长序列永远都是A,所以采用了库函数swap进行交换
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* ListNode *next;
* ListNode(int x) : val(x), next(NULL) {}
* };
*/
class Solution {
public:
ListNode *getIntersectionNode(ListNode *headA, ListNode *headB) {
// 先把长度找到,然后把指针偏移到与短序列相同的位置,然后一位一位比较,如果相同,就是找到的那个节点
ListNode *dummyNodeA = new ListNode(0);
ListNode *dummyNodeB = new ListNode(0);
dummyNodeA -> next = headA;
dummyNodeB -> next = headB;
ListNode *curA = dummyNodeA;
ListNode *curB = dummyNodeB;
int lengthA = 0, lengthB = 0;
while(curA -> next != NULL){
curA = curA -> next;
lengthA ++;
}
while(curB -> next != NULL){
curB = curB -> next;
lengthB ++;
}
cout << "length A: " << lengthA << " length B: " << lengthB << endl;
// 判断是否大小,保证A序列一定是长序列
if(lengthB > lengthA){
swap(lengthA, lengthB);
swap(dummyNodeA, dummyNodeB);
}
int vaule_offset = lengthA - lengthB;
curA = dummyNodeA;
curB = dummyNodeB;
while(vaule_offset--){
curA = curA -> next;
}
while(curA -> next != NULL){
if(curA->next == curB -> next){
return curA -> next;
}else{
curA = curA -> next;
curB = curB -> next;
}
}
return NULL;
}
};
题目四:142.环形链表II
提示: 算是链表比较有难度的题目,需要多花点时间理解 确定环和找环入口,建议先看视频。
Leetcode题目:142. 环形链表
题目链接/文章讲解/视频讲解
此任务可以分为两部分:
(1)如何判断是否有环;
(2)环的入口是在哪里
还是采用双指针法,快慢指针,快指针每次走两个,慢指针每次走一个(注意就是要按这样的速度,否则会如果快指针走三个,可能每次都跳过去。),然后只要指针相遇那就是有环,然后在相遇点,一个从相遇点走,另外一个从链表的其实位置走,再次相遇的点就是环的入口点
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* ListNode *next;
* ListNode(int x) : val(x), next(NULL) {}
* };
*/
class Solution {
public:
ListNode *detectCycle(ListNode *head) {
ListNode *fast = head;
ListNode *slow = head;
ListNode *index1, *index2;
// 判断链表是否有环
while(fast != NULL && fast->next != NULL){
slow = slow -> next;
fast = fast -> next -> next;
// 如果快慢指针相遇了
if(fast == slow){
index1 = fast;
index2 = head; // 注意慢指针是从头开始走的
while(index1 != index2){
index1 = index1 -> next;
index2 = index2 -> next;
}
return index1;
}
}
return NULL;
}
};
五、链表总结
个人感觉,链表这种数据结构相对数组来说比较复杂,主要是指针的偏移以及边界和终止条件的判定,对于链表的基本操作还是需要多加练习的。
5.1 链表的知识点总结
主要可以分为下面几个知识点:
- 链表的种类主要为:单链表,双链表,循环链表 ;
- 链表的存储方式:链表的节点在内存中是分散存储的,通过指针连在一起;
- 链表是如何进行增删改查的(一定要熟悉);
- 数组和链表在不同场景下的性能分析;
5.2 链表经典题目
- 虚拟头节点
- 链表创建和增删改查基础操作
- 反转链表
- 删除倒数第N个节点
- 链表相交;
- 环形链表
5.3 链表部分思维导图
六、个人感受
因为这是自己第一遍刷题,一般题目看到5min没思路,就直接看了卡哥的讲解视频和题解,然后算是理解了,也AC了,但总是感觉自己学的有点虚,不过我还是先把这些基础的都学了吧,也给自己定个规则:
(1)每次在刷新题目之前,需要把昨天的题目完成了;
(2)每周日的时候需要把这一周的所有题目重刷一遍。