题目描述:
给你两个单链表的头节点 headA 和 headB ,请你找出并返回两个单链表相交的起始节点。如果两个链表不存在相交节点,返回 null 。
图示两个链表在节点 c1 开始相交:
题目数据保证整个链式结构中不存在环。
注意,函数返回结果后,链表必须 保持其原始结构 。
自定义评测:
评测系统 的输入如下(你设计的程序 不适用 此输入):
intersectVal - 相交的起始节点的值。如果不存在相交节点,这一值为 0
listA - 第一个链表
listB - 第二个链表
skipA - 在 listA 中(从头节点开始)跳到交叉节点的节点数
skipB - 在 listB 中(从头节点开始)跳到交叉节点的节点数
评测系统将根据这些输入创建链式数据结构,并将两个头节点 headA 和 headB 传递给你的程序。如果程序能够正确返回相交节点,那么你的解决方案将被视作正确答案 。
示例 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 个节点。
示例 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
0 <= 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]
进阶:
你能否设计一个时间复杂度 O(n) 、仅用 O(1) 内存的解决方案?
方法一:哈希集合
HashSet哈希集合是根据对象的哈希值来确定元素在集合中的存储位置,因此具有良好的存取和查找性能。保证元素唯一性的方式依赖于:hashCode与equals方法。
所以方法一利用哈希集合首先存储A链表中的所有结点,之后再与B链表进行判断,根据哈西元素的唯一性,当判断到同一元素时,之后的每个元素也都指向同一个,即当前节点就是两个链表相交的节点。
利用哈利集合的方法简单明了易懂,但对哈希集合的掌握并不熟练,需要注意哈希集合中的常用方法,并且与哈希映射区分开来:
哈希集合:
add(value):向哈希集合中插入一个值。
contains(value) :返回哈希集合中是否存在这个值。
remove(value):将给定值从哈希集合中删除。如果哈希集合中没有这个值,什么也不做。
哈希映射:
put(key, value):向哈希映射中插入(键,值)的数值对。如果键对应的值已经存在,更新这个值。
get(key):返回给定的键所对应的值,如果映射中不包含这个键,返回-1。
remove(key):如果映射中存在这个键,删除这个数值对。
执行结果:通过
执行用时:7 ms, 在所有 Java 提交中击败了23.91%的用户
内存消耗:42.4 MB, 在所有 Java 提交中击败了5.01%的用户
通过测试用例:39 / 39
/**
* Definition for singly-linked list.
* public class ListNode {
* int val;
* ListNode next;
* ListNode(int x) {
* val = x;
* next = null;
* }
* }
*/
public class Solution {
public ListNode getIntersectionNode(ListNode headA, ListNode headB) {//哈希集合
ListNode temp=headA;
Set<ListNode> visited=new HashSet<ListNode>();
while(temp!=null){
visited.add(temp); //将链表A中的结点逐个添加到哈希集合中
temp=temp.next;
}
temp=headB; //链表A添加完毕temp结点就没啥用了,可以直接赋值为B链表的头结点
while(temp!=null){
if(visited.contains(temp)){ //判断集合中是否包含了与B链表重合的A链表中的结点
return temp;
}
temp=temp.next;
}
return null;
}
}
方法二:双指针
双指针的方法更巧妙,而且实现了空间复杂度O(1),假设链表A的前相交前的长度为a,链表B相交前的长度为b,两段链表相交后的长度均为c,具体思路如下:
1.先判断A链表与B链表是否为空,若有任意为空,则说明不相交;
2.判断两链表是否相交:
若两链表相交前长度相等,即a==b,则第一次遍历即可得到相交节点;
若两链表相交前长度不等,即a!=b,则当指向链表A的临时指针tempA为空时,将tempA指向headB;反之,则当指向链表B的临时指针tempB为空时,将tempB指向headA。这么做的目的是,
2.1若两链表相交,当tempA遍历a+c+b次,tempB遍历b+c+a次后,两个指针相遇,此时两指针均指向同一节点,直接返回即可;
2.2当两链表不相交时,tempA遍历a+c+b次,tempB遍历b+c+a次后,两个指针并不会相遇,而是均指向null,也直接返回即可;
3.最后的返回结果即为此次判断的最终结果。
不得不说——妙啊!!!且空间复杂度为O(1)!
执行结果:通过
执行用时:1 ms, 在所有 Java 提交中击败了99.98%的用户
内存消耗:41.2 MB, 在所有 Java 提交中击败了63.40%的用户
通过测试用例:39 / 39
/**
* Definition for singly-linked list.
* public class ListNode {
* int val;
* ListNode next;
* ListNode(int x) {
* val = x;
* next = null;
* }
* }
*/
public class Solution {
public ListNode getIntersectionNode(ListNode headA, ListNode headB) {//双指针
if(headA==null || headB==null){
return null;
}
ListNode tempA=headA;
ListNode tempB=headB;
//不相交-->两个指针分别遍历完两个链表后同时指向null
//相交---->两个链表遍历到相同节点,
while(tempA!=tempB){
tempA= tempA==null?headB:tempA.next;
tempB= tempB==null?headA:tempB.next;
}
return tempA;
}
}
/**
两个三目表达式等价于:
if(tempA==null){
tempA=headB;
}else{
tempA=tempA.next;
}
if(tempB==null){
tempB=headA;
}else{
tempB=tempB.next;
}
*/
平平无奇小白程序媛一枚,欢迎各位大佬交流指教,如有不正确的地方,欢迎留言改正,谢谢!!!