离思五首-元稹
曾经沧海难为水,除却巫山不是云。
取次花丛懒回顾,半缘修道半缘君。
目录
题目描述:
思路分析:
方法及时间复杂度:
法一 计算链表长度(暴力解法)
法二 栈
法三 哈希集合
法四 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语言是世界上最好的语言。
个人总结
俗话说,温故而知新。第一次看这道题时,没有想到那么多解法。随着知识面的扩张,对旧的东西又有了新的感悟。