离思五首-元稹
曾经沧海难为水,除却巫山不是云。
取次花丛懒回顾,半缘修道半缘君。
目录
题目描述:
思路分析:
方法及时间复杂度:
法一 计算链表长度(暴力解法)
法二 栈
法三 哈希集合
法四 map或unordered_map
法五 双指针(经典解法)
法六 递归(烧脑解法)
个人总结
题目描述:
相交返回相交结点,不相交返回空。
160. 相交链表 - 力扣(LeetCode)
思路分析:
要找相交的那个结点,可以让两个起始位置相同的指针遍历两个链表,当指针所指结点位置相同时,就是相交结点。简单粗暴。
也可以用哈希,栈等方法,详情如下。
方法及时间复杂度:
法一 计算链表长度(暴力解法)
计算两个链表长度m,n。再定义两个指针,要使两个指针在同一起始位置,需要使长链表的那个指针那个先走abs(m-n)步。
双指针同时走,相同则为相交。否则同时走到空。代码如下:
class Solution {
public:
ListNode *getIntersectionNode(ListNode *headA, ListNode *headB) {
int lenA=0,lenB=0;
ListNode* cur=headA;
while(cur){
++lenA;
cur=cur->next;
}
cur=headB;
while(cur){
++lenB;
cur=cur->next;
}
int gap=abs(lenA-lenB);//两链表的差距
ListNode* fast=nullptr,*slow=nullptr;
if(lenA>lenB){
fast=headA;
slow=headB;
}else{
fast=headB;
slow=headA;
}
while(gap--){
fast=fast->next;//fast先走gap步
}
while(fast){
if(fast==slow){
return fast;
}
fast=fast->next;
slow=slow->next;
}
return nullptr;
}
};
时间复杂度O(m+n),m,n为两个链表长度
空间复杂度O(1)
法二 栈
设两个栈,将两个链表的结点分别入栈,依次访问栈顶元素,如果相交,则栈顶元素相等的最后一个结点为相交结点。不相交说明栈顶元素不相同,直接返回空。
代码如下:
class Solution {
public:
ListNode *getIntersectionNode(ListNode *headA, ListNode *headB) {
stack<ListNode*> stA,stB;
ListNode* cur=headA;
while(cur){
stA.emplace(cur);
cur=cur->next;
}
cur=headB;
while(cur){
stB.emplace(cur);
cur=cur->next;
}
while(!stA.empty()&&!stB.empty()&&stA.top()==stB.top()){
cur=stA.top();
stA.pop();
stB.pop();
}
return cur;
}
};
时间复杂度O(m+n)
空间复杂度O(m+n)
法三 哈希集合
将第一个链表放入集合中,在遍历第二个链表的结点,然后在集合中查找,如果查找到的第一个结点,就是相交结点,直接返回,否则返回空。代码如下:
class Solution {
public:
ListNode *getIntersectionNode(ListNode *headA, ListNode *headB) {
unordered_set<ListNode*> hash;
ListNode* cur=headA;
while(cur){
hash.insert(cur);
cur=cur->next;
}
cur=headB;
while(cur){
if(hash.find(cur)!=hash.end()){
return cur;
}
cur=cur->next;
}
return nullptr;
}
};
时间复杂度O(m+n)
空间复杂度O(m)
法四 map或unordered_map
与法三思路相同,都用的是这几个容器的查找功能,不多解释。
class Solution {
public:
ListNode *getIntersectionNode(ListNode *headA, ListNode *headB) {
unordered_map<ListNode*, int> hash;
ListNode* cur = headA;
while (cur) {
hash[cur]++;
cur = cur->next;
}
cur = headB;
while (cur) {
if (hash.find(cur) != hash.end()) {
return cur;
}
cur = cur->next;
}
return nullptr;
}
};
时间复杂度O(m+n)
空间复杂度O(m)
map还可以这样:
class Solution {
public:
ListNode *getIntersectionNode(ListNode *headA, ListNode *headB) {
map<ListNode*,int> nodes;
ListNode* cur=headA;
while(cur){
nodes[cur]++;
cur=cur->next;
}
cur=headB;
while(cur){
nodes[cur]++;
cur=cur->next;
}
for(auto node:nodes){
if(node.second==2){
return node.first;
}
}
return nullptr;
}
};
时间复杂度O(m+n)
空间复杂度O(m+n)
法五 双指针(经典解法)
"来时路上不见你,于是便走你来时的路,相遇时才发现你也走过我曾走过的路💓"
class Solution {
public:
ListNode *getIntersectionNode(ListNode *headA, ListNode *headB) {
ListNode *Girl = headA, *Boy = headB;
while (Girl!=Boy) {
Girl=Girl==nullptr ? headB:Girl->next;
Boy = Boy==nullptr ? headA:Boy->next;
}
return Boy;
}
};
时间复杂度O(m+n)
空间复杂度O(1)
利用抽象思维和数学思维,可以将两条链表连接成一条带环链表,让两个指针同时走,环的入口就是相交结点,相逢何必曾相识,同是天涯沦落人,相交就一定会相逢,不相交都走向空。
原理:两指针相遇时,走的距离是相同的。设两条链表未相交部分长度为a,b。相交部分的长度为c。则有a+c+b==b+c+a。当一指针走向空时,则走到另一个链表的开始。
法六 递归(烧脑解法)
首先定义一个递归函数`dfs`,该函数接收3个指针参数:指向链表A当前节点的指针`curA`,指向链表B当前节点的指针`curB`,以及链表B的头节点指针`headB`。
如果A链表和B链表相交,即相交节点之后的节点全部相同,则直接返回相交节点 `(curA==curB)`,表示两个指针指向的节点相同,那么这个节点就是相交节点,直接返回即可。
如果两个指针都没有遍历到链表的结尾,则继续往下遍历,即更新指向链表B节点的指针,直到有一个链表遍历结束,进入下一步操作。
如果B链表已经到达了链表末尾,那么就把`curB`指针指向 `headB`,即链表B的头节点,同时把`curA`指针指向下一个节点,继续查找。这样做的本质是相当于把链表B的指针接到了链表A上,形成了一条新的链表,重新遍历该新链表是否存在相交节点。
如果A链表已经到达了链表末尾,那么就把`curA`指针指向`headB`,即链表B的头节点,同时把`curB`指针指向下一个节点,继续查找。这样做的本质也是相当于把链表A的指针接到了链表B上,形成了一条新的链表,重新遍历该新链表是否存在相交节点。
如果上面的步骤都没有找到相交节点,则表明两个链表不相交,返回空指针即可。
最后,在`getIntersectionNode`函数中,调用`dfs`递归函数,传入链表A的头节点`headA`,链表B的头节点`headB`,以及链表B的头节点指针`headBref`,作为参数。函数返回的即为两个链表的相交节点。代码如下:
struct ListNode *dfs(struct ListNode *curA, struct ListNode *curB,struct ListNode *headB){
if(curA==curB){
return curB;
}
if(curA!=NULL&&curB!=NULL){
return dfs(curA,curB->next,headB);
}
else if(curB==NULL&&curA!=NULL){
return dfs(curA->next,headB,headB);
}else{
return NULL;
}
}
struct ListNode *getIntersectionNode(struct ListNode *headA, struct ListNode *headB) {
return dfs(headA,headB,headB);
}
时间复杂度O(m+n)
空间复杂度O(max(m,n))
用c++超时,用c语言不会。毕竟c语言是世界上最好的语言。
个人总结
俗话说,温故而知新。第一次看这道题时,没有想到那么多解法。随着知识面的扩张,对旧的东西又有了新的感悟。