最近听了左神的算法课,对一些常用数据结构以及算法改进的思路有了更深的理解,特此总结,不定期更新算法题目以及答案总结!笔者使用C++进行算法重现!虽然左神使用的是JAVA,但他自己也说了,算法与语言无关,但C++写出来的复杂度过不了,那么用其他的语言JAVA,Python也一定过不了!所以刷题还是尽量C++吧,算法基本用不了什么库函数,顶多几个数据结构,而C++的STL里面都包含。
单向链表
首先什么是单向链表,其实质也很简单,也就是有一个自定义的节点,其内部有且只有一个指针指向下一节点。但是一定要注意,单向链表分为单向有环链表和单向无环链表,其中单向无环链表的末节点为null。所以在面试时一定要注意题目的单向链表有没有说有环还是无环!
题目:两个单链表相交的第一个节点
在本题中,单链表可能有环,也可能无环。给定两个 单链表的头节点 head1和head2,这两个链表可能相交,也可能 不相交。请实现一个函数, 如果两个链表相交,请返回相交的 第一个节点;如果不相交,返回null 即可。 要求:如果链表1 的长度为N,链表2的长度为M,时间复杂度请达到 O(N+M),额外 空间复杂度请达到O(1)
首先我们先画出链表相交的几种形式,如下图所有,共有四种情况:
- 两个无环单向链表相交
- 两个有环单向链表相交,但入环节点不在同一个节点
- 两个有环单向链表相交,但入环节点为同一个节点
- 两个单向链表不相交
- 注意,情况四不是单向链表结构,其中间节点内部必有两个指针
我们分析好了之后,开始Coding......
首先建立一个函数,判断一个单向链表是否有环,如果有则返回入环节点,如果没有返回null.在这里使用快慢指针的方法:(也可以使用set,但需要辅助空间)
- 快指针走两步, 满指针走一步,如果有环则两者必在一个节点重合
- 重合以后,快指针回到原来出发节点,改为步长为1
- 快慢指针再次走(步长为1),重合节点即为目标节点
如果不信,可以自己试试,数学能力好的可以自己证明!
// 如果没有环返回null, 如果有环返回入环的第一个节点(快慢指针的方式)
接着,经过上面的函数后,我们会得到两种情况:有环链表相交和无环链表相交!但有人问,可不可以一个有环和一个无环相交?绝对不可能,这是单向链表!!!
并且,经过分析后,发现有环链表相交的情况为两种(情况二和情况三),而无环链表相交的情况只有Y字型一种(情况一)。
两个无环链表相交
这个就很简单了,也就是我画的情况一,Y字型,首先遍历两个链表,得出两个链表的长度差n,然后让长链表先遍历n个节点,接着两个链表同时遍历,直到节点相同,则相同节点为目标节点。一定要注意长度差的计算,并且如何区分长短链表!
那么怎么判断不相交呢?对于无环链表来说,相交的情况只存在Y字型,因此只要比对最后节点是不是相同就可以了,因为无环相交的最后一个节点必定相同!
Node* noLoop(Node* head1, Node* head2){ // 只能是Y字型结构
if (head1 == nullptr || head2 == nullptr){
return nullptr;
}
Node* cur1 = head1;
Node* cur2 = head2;
int len1 = 0, len2 = 0;
while(cur1->next != nullptr){
len1++;
cur1 = cur1->next;
}
while(cur2->next != nullptr){
len2++;
cur2 = cur2->next;
}
if(cur1 != cur2){ // 如果有公共部分,必定指向统一节点
return nullptr;
}
int n = len1 - len2;
cur1 = (n > 0) ? head1 : head2; // cur1为较长的list
cur2 = cur1 == head1 ? head2 : head1; // cur2为较短的list
n = n < 0 ? -1*n : n; // 取绝对值
while(n != 0){
n --;
cur1 = cur1->next;
}
while(cur1 != cur2){
cur1 = cur1->next;
cur2 = cur2->next;
}
return cur1;
}
两个有环链表相交
有环链表相交分为两种情况,即图中的情况二和情况三,也就是入环节点在不在同一个节点?
- 入环节点在同一个节点:那么查找第一个相交节点的方法就和无环链表相交节点的查询方式一样了,只不过是终止点由null变成了loop(入环节点)
- 入环节点不在同一个节点:那么就从第一个入环节点loop1开始遍历,如果遇到了第二个入环节点loop2,那么说明第一个节点为目标节点,如果没有遇到,那说明这两个链表都是有环单向链表,但是不相交!返回null。
Node* bothLoop(Node* head1, Node* loop1, Node* head2, Node* loop2){
Node* cur1 = nullptr;
Node* cur2 = nullptr;
if(loop1 == loop2){ // Y型下面有一个环,和Y型类似
cur1 = head1;
cur2 = head2;
int n = 0;
while(cur1 != loop1){
n++;
cur1 = cur1->next;
}
while(cur2 != loop2){
n--;
cur2 = cur2->next;
}
cur1 = n > 0 ? head1 : head2;
cur2 = cur1 == head1 ? head2 : head1;
n = n < 0 ? -1*n : n;
while(n != 0){
n--;
cur1 = cur1->next;
}
while(cur1 != cur2){
cur1 = cur1->next;
cur2 = cur2->next;
}
return cur1;
}else{
cur1 = loop1->next;
while(cur1 != loop1){
if (cur1 == loop2){
return loop1; // 天线宝宝型的拓扑结构
}
cur1 = cur1->next;
}
return nullptr; // 两个有环链表不相交
}
}
由于C++中new出来的节点,需要进行delete进行内存释放,但笔者在测试文件中只写了单向无环链表的内存释放函数,而有环的如何释放笔者也有点迷茫,有大佬知道的还请告知一下!
完整测试文件(C++版),文件名为:两个单向链表(有环无环)第一个相交节点。请关注我的个人公众号 (算法工程师之路),回复"左神算法基础CPP"即可获得,并实时更新!希望大家多多支持哦~
公众号简介:分享算法工程师必备技能,谈谈那些有深度有意思的算法,主要范围:C++数据结构与算法/深度学习(CV),立志成为Offer收割机!坚持分享算法题目和解题思路(Day By Day)