面试题 02.07. 链表相交
题目
要求
给你两个单链表的头节点 headA 和 headB ,请你找出并返回两个单链表相交的起始节点。如果两个链表没有交点,返回 null 。
思路和答案
这道题目先用暴力破解,直接使用双层 for 循环,如下:
/**
* 暴力破解,双层 for 循环
*
* @param headA
* @param headB
* @return
*/
public ListNode getIntersectionNode(ListNode headA, ListNode headB) {
ListNode virtualA = headA;
while (virtualA != null) {
ListNode virtualB = headB;
while (virtualB != null) {
if (virtualB == virtualA) {
return virtualA;
}
virtualB = virtualB.next;
}
virtualA = virtualA.next;
}
return null;
}
想一想有没有比这个更好的解决方案呢,可以考虑使用集合呀,Set 和 List 都可以,遍历一个链表,放入集合,遍历另外一个链表,判断是否包含在内,如下:
/**
* 单层循环,借助 List 结构
*
* @param headA
* @param headB
* @return
*/
public ListNode getIntersectionNode2(ListNode headA, ListNode headB) {
ListNode virtualA = headA;
ListNode virtualB = headB;
List<ListNode> listA = new ArrayList<>();
while (virtualA != null) {
listA.add(virtualA);
virtualA = virtualA.next;
}
while (virtualB != null) {
if (listA.contains(virtualB)) {
return virtualB;
}
virtualB = virtualB.next;
}
return null;
}
上面两个题目前者的时间复杂度是 O(m * n),空间复杂度O(1);后者的时间复杂度为O(m + n),空间复杂度为 O(m),用空间换取时间了,这个解决方案在 leetcode 上执行慢的原因在于listA.contains
消耗时间,contains
底层也是通过 for 循环遍历的,所以这里使用 HashMap 替换 List,可以提高性能,如下:
public ListNode getIntersectionNode(ListNode headA, ListNode headB) {
ListNode virtualA = headA;
ListNode virtualB = headB;
HashMap<ListNode, Integer> map = new HashMap<>();
while (virtualA != null) {
map.put(virtualA, virtualA.val);
virtualA = virtualA.next;
}
while (virtualB != null) {
if (map.get(virtualB) != null) {
return virtualB;
}
virtualB = virtualB.next;
}
return null;
}
接下来说说双指针,想到用双指针,但是没想到怎么用,就是在处理指针如果遍历到链表的结尾应该怎么处理,看了题解之后发现,还可以在指针遍历到链表结尾之后转去遍历另外一个链表,举个简单的例子,链表 A 为 1→2→3→4→null
,链表 B 为 5→6→7→8→3→4→null
,这个时候指针 C 遍历列表 A,指针 D 遍历列表 B,当 C 遍历到 A 的尾部之后转去遍历链表 B,D 遍历到 B 的结尾之后转去遍历 A,这样下次 C 和 D 遍历的实际链表分别为:
1→2→3→4→5→6→7→8→3→4→null
5→6→7→8→3→4→1→2→3→4→null
上下对比一下就知道结果了,那代码怎么写呢?如下:
public ListNode getIntersectionNode(ListNode headA, ListNode headB) {
ListNode pointA = headA;
ListNode pointB = headB;
boolean ab = true;
boolean ba = true;
while (pointA != null && pointB != null) {
if (pointA == pointB) {
return pointA;
}
pointA = pointA.next;
pointB = pointB.next;
// 声明 ab 和 ba 的目的是为了防止死循环
if (pointA == null && ab) {
pointA = headB;
ab = false;
}
if (pointB == null && ba) {
pointB = headA;
ba = false;
}
}
return null;
}