题目
已知链表节点数据结构ListNode如下:
/**
* 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) {
// 使用哈希集合存储链表节点
Set<ListNode> visited = new HashSet<ListNode>();
// 首先遍历链表 headA,并将链表 headA 中的每个节点加入哈希集合中。
ListNode temp = headA;
while(temp != null) {
visited.add(temp);
temp = temp.next;
}
// 然后遍历链表 headB,对于遍历到的每个节点,判断该节点是否在哈希集合中
temp = headB;
while(temp != null) {
// 如果当前节点在哈希集合中,则后面的节点都在哈希集合中,返回该节点
if(visited.contains(temp)) {
return temp;
}
// 如果当前节点不在哈希集合中,则继续遍历下一个节点
temp = temp.next;
}
// 如果链表 headB 中的所有节点都不在哈希集合中,
// 则两个链表不相交,返回 null。
return null;
}
}
分析
关于为什么在遍历链表 headB 时 “如果当前节点在哈希集合中,则后面的节点都在哈希集合中,即从当前节点开始的所有节点都在两个链表的相交部分”:由hashSet的实现原理决定的。
HashSet 实现原理
1. HashSet 简介
HashSet
是基于 HashMap
实现的一种不允许有重复元素的集合,但允许存在 null
值。
2. 去重原理
HashSet
通过 hashCode()
和 equals()
方法实现去重。具体过程如下:
- 对象加入
HashSet
时,计算对象的hashCode
值来确定加入的位置。 - 如果该位置没有值,则直接放入。
- 如果有值,则通过
equals()
方法检查两个对象是否相同,如果相同则加入失败,否则重新散列到其他位置。
注意:
- 两个对象相等(即适用于
equals(Object)
方法),它们的hashCode
必须相等。 hashCode
相等,两个对象不一定相等。
时间复杂度:O(m+n),
其中 m 和 n 是分别是链表 headA 和 headB 的长度。需要遍历两个链表各一次。
空间复杂度:O(m),
其中 m 是链表 headA 的长度。需要使用哈希集合存储链表 headA 中的全部节点。
题解二(双指针)
public class Solution {
public ListNode getIntersectionNode(ListNode headA, ListNode headB) {
if (headA == null || headB == null) {
return null;
}
ListNode pA = headA, pB = headB;
while (pA != pB) {
pA = pA == null ? headB : pA.next;
pB = pB == null ? headA : pB.next;
}
return pA;
}
}
分析
优点:
使用双指针的方法,可以将空间复杂度降至 O(1)
指针初始和移动过程:
创建两个指针 pA 和 pB,初始时分别指向两个链表的头节点 headA 和 headB,然后将两个指针依次遍历两个链表的每个节点。每步操作需要同时更新指针 pA 和 pB。
如何更新:无论pA、pB, 只要指针不为空,就将指针移到下一个节点; 如果指针为空,就将指针移到对方链表的头结点。
判断是否相交的条件、何时返回:
当指针 pA 和 pB 指向同一个节点或者都为空时,返回它们指向的节点或者 null。
原理:a+c+b = b+c+a 、m+n = n+m
按照约定的移动步骤,如果两链表有交点,两指针一定同时到达交点;如果两链表没有交点,两指针一定同时变为null,无论有无交点,当两指针第一次相等时,即可作出判断。因此 循环退出条件是 pA != pB。