链表也能相交~
- 一、题目描述
- 二、思路分析与罗列
- 三、整体代码展示
- 四、总结与提炼
一、题目描述
原题传送门
示例 1:
输入:intersectVal = 8, listA = [4,1,8,4,5], listB = [5,6,1,8,4,5], skipA = 2, skipB = 3
输出:Intersected at ‘8’
解释:相交节点的值为 8 (注意,如果两个链表相交则不能为 0)。
从各自的表头开始算起,链表 A 为 [4,1,8,4,5],链表 B 为 [5,6,1,8,4,5]。
在 A 中,相交节点前有 2 个节点;在 B 中,相交节点前有 3 个节点。
— 请注意相交节点的值不为 1,因为在链表 A 和链表 B 之中值为 1 的节点 (A 中第二个节点和 B 中第三个节点) 是不同的节点。换句话说,它们在内存中指向两个不同的位置,而链表 A 和链表 B 中值为 8 的节点 (A 中第三个节点,B 中第四个节点) 在内存中指向相同的位置。
示例 2:
输入:intersectVal = 2, listA = [1,9,1,2,4], listB = [3,2,4], skipA = 3, skipB = 1
输出:Intersected at ‘2’
解释:相交节点的值为 2 (注意,如果两个链表相交则不能为 0)。
从各自的表头开始算起,链表 A 为 [1,9,1,2,4],链表 B 为 [3,2,4]。
在 A 中,相交节点前有 3 个节点;在 B 中,相交节点前有 1 个节点。
示例 3:
输入:intersectVal = 0, listA = [2,6,4], listB = [1,5], skipA = 3, skipB = 2
输出:null
解释:从各自的表头开始算起,链表 A 为 [2,6,4],链表 B 为 [1,5]。
由于这两个链表不相交,所以 intersectVal 必须为 0,而 skipA 和 skipB 可以是任意值。
这两个链表不相交,因此返回 null 。
提示:
- listA 中节点数目为 m
- listB 中节点数目为 n
- 1 <= m, n <= 3 * 104
- 1 <= Node.val <= 105
- 0 <= skipA <= m
- 0 <= skipB <= n
- 如果 listA 和 listB 没有交点,intersectVal 为 0
- 如果 listA 和 listB 有交点,intersectVal == listA[skipA] == listB[skipB]
看完了上面这些题目的描述,相信很多同学都被此题吓住了,觉得这个题目很难,自己做不出来,但是进到原题中可以看到这是一道力扣上的简单题,正确率也不低,有65%左右,所以不要害怕,我带你来慢慢分析一下,如何去求解本题⌛️
二、思路分析与罗列
- 让我猜猜,若是没有题目给出的图示你是不是认为两个相交的链表应该是这样的😃
- 哈哈,其实我也有想到过,但是后来一想这个只是单链表,到结点的相交处怎么指向后面的两个方向呢,所以果断丢弃了这个思想
- 就像下面这样,因为交点处的结点时只有一个next,两个相交的链表只是一个普通的单链表,因此是不会存在下面这种结构的
- 但是呢,实际上它是这样的,呈现一个【Y】字形
- 以上这些是我在没看本题前的一个思考过程,这个点其实也挺重要的,对于一道题目,每个人都有它自己的思考过程,也不是在一开始就会有思路。刷题的时候我们应该开动自己的大脑,把自己想到的画出来,试试看,行不行得通,若是行不通,就立马换其他思路,在这个思考的过程中,你对这道题也有了一个逐步的认识
- 好了,废话不多说,我们正式开始分析本题的思路
- 以下是我对于这一道题的思路罗列
好,看完题目的描述,我们来分析一下去求解这道题目,
- 首先我们从案例一可以看到,对于两个链表的相交,就是当两个链表向后遍历的时候会碰到重合的结点,我们可以根据这个特点先对链表做一个遍历。先看看他们是否相交
- 我的思路是这样的:首先一点是不能动两个链表的头,采用【curA】和【curB】去代替遍历,遍历结束的条件就是这两个指针的next指向NULL。
- 在他们都遍历结束后对两个指针进行一个判断,若是相同,则表示两个链表一定在某一处汇聚到一起了,若是不同则表示两个链表不相交。这就是第一步,我们来看看代码
ListNode* curA = headA, *curB = headB; //遍历
int lenA = 0, lenB = 0; //记录两个链表的长度
while(curA->next){
lenA++;
curA = curA->next;
}
while(curB->next){
lenB++;
curB = curB->next;
}
if(curA != curB)
return NULL; //若两个结点的地址不同,则表示不相交,return NULL
- 可以看到在循环内部出现了【lenA】和【lenB】,这个是用来在遍历链表时记录这个链表的长度,我们后面马上会用到
- 接着第二步,我们就要开始考虑如何去求这两个链表的交点,但是在这之前呢,还要做一步,就是求出较长的那个链表,因为我们无法知晓较长的那个链表是什么,因此在后面求交点时就不知道需要遍历多少次才能碰到交点,所以这个时候我作出一个假设:
- 假设链表A是长的那个,链表B是短的那个。但这始终是我们的假设,并不成立,所以此时需要使用到我上面求出的【lenA】和【lenB】,对他们作出一个比较,若是【lenB > lenA】,则表示我们的假设错误,更新一下长的链表和短的链表,若是【lenA > lenB】,则无需进行判断,表示我们假设成功,直接使用我们一开始假设好的就行。代码是这样的
ListNode* longer = headA, *shorter = headB; //先假设链表A长于链表B
if(lenB > lenA){ //若是假设错误则交换
longer = headB;
shorter = headA;
}
- 接着我们便求出了较长的那个链表,此时只需要求出两个链表的长度差,记为【gap】。然后让长的那一个链表先走【gap】步,就可以让两个链表在同一起跑线上了。代码如下
int gap = abs(lenA - lenB); //求出两个链表的长度差
while(gap--)
longer = longer->next; //先让长的链表走gap步,使得两链表位于同一起跑线
- 完成了前面两步,最后一步我们就可以从两个链表现在的起始位置开始遍历,然后判断他们在中途是否相交与一个结点,此时直接return 就是交点
while(longer != shorter) //一同往后走,寻找两链表的交点(此时一定相交)
{
longer = longer->next;
shorter = shorter->next;
}
return longer;
三、整体代码展示
展示一下整体的代码,不过还是自己去敲一遍比较好哦
/**
* 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* curA = headA, *curB = headB; //遍历
int lenA = 0, lenB = 0; //记录两个链表的长度
while(curA->next){
lenA++;
curA = curA->next;
}
while(curB->next){
lenB++;
curB = curB->next;
}
if(curA != curB)
return NULL; //若两个结点的地址不同,则表示不相交,return NULL
ListNode* longer = headA, *shorter = headB; //先假设链表A长于链表B
if(lenB > lenA){ //若是假设错误则交换
longer = headB;
shorter = headA;
}
int gap = abs(lenA - lenB); //求出两个链表的长度差
while(gap--)
longer = longer->next; //先让长的链表走gap步,使得两链表位于同一起跑线
while(longer != shorter) //一同往后走,寻找两链表的交点(此时一定相交)
{
longer = longer->next;
shorter = shorter->next;
}
return longer;
}
};
四、总结与提炼
- 最后我们来总结一下本文所介绍的内容,本文讲解的是一道力扣中有关求解两个链表的相交结点的题目,对于本题虽然看上去比较复杂,但是经过我们的一顿分析之后,你是不是觉得本题并不是很难,因为有了这么一个分析的过程,其实你已经开始思考了,随着一步步解决方案的罗列,也就有了思路,此时再将每一步的思路慢慢转化为代码,其实做题也没有那么难,难的只是这个分析的过程罢了😄
以上就是本文所要描述的所有内容,感谢您对本文的观看,如有疑问请于评论区留言或者私信我都可以🍀