经典例题如下:
剑指offer52/力扣160
ps:这两道题实质上是一样的
这是一道经典的算法题,下面我将介绍几种解题思路:
- 哈希表:先遍历链表 A,将每个节点的地址存储到哈希表中。然后遍历链表 B,对于每个节点,判断它是否在哈希表中出现过。时间复杂度 O(m+n),空间复杂度 O(m) 或 O(n),其中 m 和 n 分别为链表 A 和 B 的长度。
- 双指针法:同时遍历链表 A 和链表 B,当指针 pA 和指针 pB 相遇时,即为相交节点。原理是将两个链表拼接起来后,由于长度不同,第一次相遇的节点即为相交节点。时间复杂度 O(m+n),空间复杂度 O(1)。
- 暴力法:对于链表 A 的每个节点,都依次遍历链表 B 的每个节点,判断是否相等。时间复杂度为 O(mn),空间复杂度为 O(1)。
以上三种算法都是常见的解法,其中双指针法是最优解,也是最推荐的解法。 暴力法是最不推荐的解法。
在选择解法的时候,尽量考虑一下时间复杂度和空间复杂度。
方法一:双指针法
这道题可以使用双指针的方法来解决。
首先遍历两个链表,分别得到它们的长度。
然后让较长的链表的指针先移动若干步,直到两个链表的长度相等。
接着,同时遍历两个链表,比较每个节点是否相同,若相同则返回该节点,若遍历结束后没有相同节点,则说明两个链表不相交,返回 null。
a+x+b=b+x+a,x为重叠的部分
两个指针来回都走一遍肯定会相交
下面是 Python 代码实现:
# 双指针法的代码实现
# Definition for singly-linked list.
# class ListNode(object):
# def __init__(self, x):
# self.val = x
# self.next = None
class Solution(object):
def getIntersectionNode(self, headA, headB):
pa, pb = headA, headB
while pa != pb:
pa = pa.next if pa else headB
pb = pb.next if pb else headA
return pa
#如果遇到了'return' outside function报错
#请注意缩进问题,保证return在函数内部。return 语句只能包含在函数中。
#这是因为 return 语句将值从函数发送到主程序。如果没有从中发送值的函数,return 语句将没有明确的目的。
#python非常注重缩进
下面是 cpp 代码实现:
/**
* 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 *p1=headA,*p2=headB;
if(p1==nullptr||p2==nullptr)return nullptr;
while(p1!=p2){
p1=p1==nullptr?headB:p1->next;
p2=p2==nullptr?headA:p2->next;
}
return p1;
}
};
下面是 Java代码实现:
/**
* Definition for singly-linked list.
* public class ListNode {
* int val;
* ListNode next;
* ListNode(int x) {
* val = x;
* next = null;
* }
* }
*/
class Solution {
ListNode getIntersectionNode(ListNode headA, ListNode headB) {
ListNode p1=headA,p2=headB;
if(headA==null||headB==null)return null;
while(p1!=p2){
p1=p1==null?headB:p1.next;
p2=p2==null?headA:p2.next;
}
return p1;
}
}
方法二:差和双指针
同样是双指针。
还可以先遍历获得a、b两个链表的长度m、n,差值d=m-n。开始遍历的时候长的那一个链表不从头开始,而从第d个节点开始,所以如果a、b相交,则一定会移动到同一节点上。
下面是 Python 代码实现:
class Solution(object):
def getIntersectionNode(self, headA, headB):
#class Solution:
# def getIntersectionNode(self, headA: ListNode, headB: ListNode) -> ListNode:
lenA, lenB = 0, 0
curA, curB = headA, headB
while curA:
lenA += 1
curA = curA.next
while curB:
lenB += 1
curB = curB.next
curA, curB = headA, headB
if lenA > lenB:
for i in range(lenA - lenB):
curA = curA.next
elif lenB > lenA:
for i in range(lenB - lenA):
curB = curB.next
while curA and curB:
if curA == curB:
return curA
curA = curA.next
curB = curB.next
return None
另外,可以加上一些 emoji 给代码增加一些趣味性:(但是这个在力扣上跑不通,只是好玩罢了😋)
class Solution:
def getIntersectionNode(self, headA, headB):
😎, 🐇 = 0, 0
🐢, 🐕 = headA, headB
while 🐢:
😎 += 1
🐢 = 🐢.next
while 🐕:
🐇 += 1
🐕 = 🐕.next
🐢, 🐕 = headA, headB
if 😎 > 🐇:
for i in range(😎 - 🐇):
🐢 = 🐢.next
elif 🐇 > 😎:
for i in range(🐇 - 😎):
🐕 = 🐕.next
while 🐢 and 🐕:
if 🐢 == 🐕:
return 🐢
🐢 = 🐢.next
🐕 = 🐕.next
return None
下面是 Java代码实现:
/**
* Definition for singly-linked list.
* public class ListNode {
* int val;
* ListNode next;
* ListNode(int x) {
* val = x;
* next = null;
* }
* }
*/
class Solution {
ListNode getIntersectionNode(ListNode headA, ListNode headB) {
ListNode p1=headA,p2=headB;
if(p1==null||p2==null)return null;
int l1=0,l2=0;
while(p1!=null){
p1=p1.next;
l1++;
}
while(p2!=null){
p2=p2.next;
l2++;
}
p1=headA;
p2=headB;//千万别漏了这一步
int d=0;//d为差值
if(l1>l2){
d=l1-l2;
for(int i=0;i<d;i++){
p1=p1.next;
}
}else{
d=l2-l1;
for(int i=0;i<d;i++)
p2=p2.next;
}
//求d可以简写成d=l1>l2?l1-l2:l2-l1;但是在后面依然有判断长度+for循环的步骤
while(p1!=p2){
p1=p1.next;
p2=p2.next;
}
return p1;
}
};
下面是 cpp代码实现:
/**
* 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 *p1=headA,*p2=headB;
if(p1==nullptr||p2==nullptr)return nullptr;
int l1=0,l2=0;
while(p1!=nullptr){
p1=p1->next;
l1++;
}
while(p2!=nullptr){
p2=p2->next;
l2++;
}
p1=headA,p2=headB;//千万别漏了这一步
int d=0;//d为差值
if(l1>l2){
d=l1-l2;
for(int i=0;i<d;i++){
p1=p1->next;
}
}else{
d=l2-l1;
for(int i=0;i<d;i++)
p2=p2->next;
}
//求d可以简写成d=l1>l2?l1-l2:l2-l1;但是在后面依然有判断长度+for循环的步骤
while(p1!=p2){
p1=p1->next;
p2=p2->next;
}
return p1;
}
};
//我就不写c语言的实现了,本质上是一样的,nullptr改成null即可,因为在c语言中没有nullptr
记录我的一次小失误:
写for循环的时候,“;”打错了,写成了“,”,for(int i=0,i<d;i++),报错提示重复定义i,我当时还奇怪,找了半天没找到错在哪,最后才发现是一个标点的问题
方法三:哈希
代码在官方题解里面有,我就不写了,官方写得比我好多了,实在是不敢献丑。
方法四:暴力枚举
暴力枚举时间复杂度太高,就不写了。
链表
链表长度求法
cpp
ListNode *pA=headA;//指向表头
int l1=0;//长度先初始化,在这里必须要赋初值
while(pA!=nullptr){//注意c++中为nullptr
pA=pA->next;
l1++;
}
Java
ListNode pA=headA;
int l1=0;
while(pA!=null){
pA=pA.next;
l1++;
}
c语言
ListNode *pA=headA;//指向表头
int l1=0;//长度先初始化,在这里必须要赋初值
while(pA!=null){//注意c中为null
pA=pA->next;
l1++;
}
python
lenA, lenB = 0, 0
curA, curB = headA, headB
while curA:
lenA += 1
curA = curA.next
总结
这种题目实际上是要求我们找到两个链表的公共部分,我们可以通过以下思路解决问题:
- 先分别遍历两个链表,得到它们的长度和尾节点。
- 如果两个链表相交,那么它们的尾节点一定相同。 如果它们不相交,那么它们的尾节点不相同。
- 如果两个链表相交,那么它们的公共部分一定在末尾,因此我们可以将较长的链表先遍历一定的长度,使得两个链表剩余长度相等。
- 这之后,我们同时遍历两个链表,直到找到第一个相同节点,即为它们的相交起始节点。如果没有找到相交节点,返回 null。
具体实现时,我们可以采取双指针法。首先将两个指针分别指向 headA 和 headB ,然后执行如下操作:
- 如果两个指针都不为空,则继续往后遍历。
- 如果其中一个指针为空,则将其指向另一个链表的头结点。
- 当两个指针相遇的时候,就是相交节点。