目录
一、做题心得
今天是代码随想录训练营打卡的第三天。
依旧学习的是链表部分,共做了四道相关的力扣题。感觉难度不算大,但是也有很多需要注意的细节,尤其是删除链表的倒数第n个节点那道题,不仅需要我们通过画图思考找到倒数第n个节点的具体位置,也再次考察了我们对虚拟头节点,双指针的运用。今天的题有的也运用到了哈希的相关知识,话不多说,直接开始分析今天的题。
二、题目及题解
1.LeetCode 24. 两两交换链表中的节点
题目链接:24. 两两交换链表中的节点 - 力扣(LeetCode)
这个题的要求是两两交换节点,我们很容易想到的就是递归:分两个为一组,每组进行同样的操作,并将所有节点串联起来。这里有一个简单的思路,分别记第一个,第二个,第三个节点为first,second,third,进行交换时,前两个节点相互交换,交换完之后对后续节点进行同样的操作,接在它们后边,这里我们用递归来实现这样重复进行的操作。(当然不要忘了考虑链表为空或者只有一个节点的情况)
class Solution {
public:
ListNode* swapPairs(ListNode* head) {
if(head == nullptr || head->next == nullptr)
return head;
ListNode* first = head;
ListNode* second = first->next;
ListNode* third = first->next->next;
second->next = first;
first->next = swapPairs(third); //交换完first,second之后,用递归使后边的节点进行同样的操作
return second;
}
};
2.LeetCode 19.删除链表的倒数第N个节点
题目链接:19. 删除链表的倒数第 N 个结点 - 力扣(LeetCode)
这个题的题意很简单,就是删除某个节点。但是很多人看到是倒数第n个节点,就很容易没了思路,但其实倒数第n个节点就是Length-n+1个节点。在这里我们可以想到双指针的做法,创建fast,slow指针,先让fast走n步,再两个同时走,当fast到达链表末尾的时候,slow就走到了目标节点的前一个(不过这里要注意fast与slow的开始位置)。
这里先给大家看一下我错误的解法,相信也会有很多人和我一样:
在这里我还是犯了和昨天同样的错误(昨天打卡的203.移除链表元素),没有将删除头节点的情况考虑进来(这里我们需要使用虚拟头节点),而且对于最后slow->next是否为目标节点也没思考清楚。
这里再看看正确的代码:
class Solution {
public:
ListNode* removeNthFromEnd(ListNode* head, int n) {
ListNode* dummyHead = new ListNode(0,head);
ListNode* fast = head; //fast也可初始化指向虚拟头节点,开始循环变为循环n+1次
ListNode* slow = dummyHead; //slow必须初始化为虚拟头节点,这样才能把删除头节点的情况考虑进去
while(n--)
{
fast = fast->next;
}
while(fast) //注意这里是fast,而不是fast->next(最后还要进行一步fast = fast->next)
{
slow = slow->next;
fast = fast->next;
}
slow->next = slow->next->next; //此时slow->next即是倒数第n个元素
return dummyHead->next;
}
};
为么这里slow->next就是我们要删除的节点呢?大家可以试着画画图,尝试举几个例子(比如规定链表长度和N判断目标位置),运用数学知识,得出结果·。
3.LeetCode 面试题02.07. 链表相交
题目链接:面试题 02.07. 链表相交 - 力扣(LeetCode)
第一种解法:哈希
这题乍眼一看,很容易想到哈希的解法,就是通过定义一个哈希数组,将A中节点全部存进去,再挨个遍历B中节点,当B中出现第一个与A有重复的节点时(通过哈希常用函数),就是A与B相交的节点(注意是第一个找到的重复的节点)。
代码如下:
class Solution {
public:
ListNode *getIntersectionNode(ListNode *headA, ListNode *headB) {
unordered_set<ListNode*> visited;
ListNode* temp = headA;
while(temp != nullptr)
{
visited.insert(temp); //先存放A中所有节点
temp = temp->next;
}
temp = headB;
while(temp != nullptr) //再在B中挨个遍历,查看是否有A已经存放的节点
{
if(visited.count(temp))
return temp; //一找到相同的就立刻停止,这样才是第一个相交节点
temp = temp->next;
}
return 0;
}
};
这里用到了哈希常用的两个函数:insert()与count()
关于哈希表相关知识不熟悉的话可以相应搜索下,明天我做题打卡就会是相关题目,到时候来个相关知识总结。
第二种解法:双指针
个人感觉更像是考察数学关系,我自己也没有想到,看到题解,感觉蛮有意思。
这里摘自题解评论区的一句话:“来时路上不见你,于是便走你来时的路,相遇时才发现你也走过我曾走过的路。”(浪漫至死不渝)我个人的理解是:a走完A的路,再走B的路,共走了A+B;b走完B的路,再走A的路,共走了B+A,此时走的路程相等,便会相遇,相遇处即是交点。
代码如下:
class Solution {
public:
ListNode *getIntersectionNode(ListNode *headA, ListNode *headB) {
if(headA == nullptr || headB == nullptr)
return nullptr;
ListNode* a = headA;
ListNode* b = headB;
while(a != b) //a,b指向元素不同时循环(若不相交,最后也同时指向nullptr)
{
if(a != nullptr) a = a->next; //两个判断语句(或者用条件运算符更简洁)
else a = headB;
if(b != nullptr) b = b->next;
else b = headA;
}
return a; //此时a,b都指向相交节点,若不相交,则此时都为nullptr
}
};
4.LeetCode 142.环形链表II
这个题有上个题的基础就很简单了,主要就是看懂题意,题目要求是得到进入环的第一个节点,而进入一个环的第一个节点就是通过不断往后遍历重复的第一个节点,我们首先想到的就是哈希的思路,这里就不做过多解释。
直接上代码:
class Solution {
public:
ListNode *detectCycle(ListNode *head) {
unordered_set<ListNode*> visited;
ListNode* cur = head;
while(cur)
{
if(visited.count(cur)) //一个节点第二次出现,则为进入环的第一个节点
return cur;
visited.insert(cur);
cur = cur->next;
}
return nullptr;
}
};
相信仔细做了上一道题的能轻松做出来。
这道题还可以用双指针(快慢指针),感兴趣的可以去了解下。
三、小结
今天的打卡也是成功来到了终点,感觉今天的题更清晰地加深了我对于虚拟头节点的认识(只要看到删除节点就想到它),也算是有所收获,至此,链表的打卡也就到此结束了,明天将开启哈希表的学习。我是算法小白,但也希望终有所获。