相交链表三种解法详解
今天我来详细讲解"相交链表"的三种解法:暴力法、哈希表法和双指针法。我会从时间复杂度的角度进行深入分析,帮助你理解每种方法的优缺点。
问题描述
给定两个单链表,判断它们是否相交,并返回相交点。如果相交,返回相交节点;如果不相交,返回null。
1. 暴力解法
var getIntersectionNode = function(headA, headB) {
let a = headA;
while (a !== null){
let b = headB;
while (b !== null){
if (a === b){
return a;
} else{
b = b.next;
}
}
a = a.next;
}
return null
}
时间复杂度分析
- 外层循环:遍历链表A,时间复杂度为O(m),其中m是链表A的长度。
- 内层循环:对链表A的每个节点,都需要遍历整个链表B,时间复杂度为O(n),其中n是链表B的长度。
- 总时间复杂度:O(m × n)
优缺点
- 优点:实现简单直观,空间复杂度为O(1)
- 缺点:时间复杂度高,当链表很长时(如m=10^4, n=10^4),需要10^8次操作,效率极低
2. 哈希表解法
var getIntersectionNode = function(headA, headB) {
const hashMap = new Map();
let a = headA;
let b = headB;
while (a){
hashMap.set(a, 1);
a = a.next;
}
while (b){
if (hashMap.has(b)){
return b;
}
b = b.next
}
return null
}
时间复杂度分析
- 遍历链表A:将每个节点存入哈希表,时间复杂度为O(m)
- 遍历链表B:对每个节点查询哈希表,哈希表查询平均时间复杂度为O(1),总时间复杂度为O(n)
- 总时间复杂度:O(m + n)
优缺点
- 优点:时间复杂度较低,比暴力法高效得多
- 缺点:需要额外空间O(m)来存储哈希表
3. 双指针解法
var getIntersectionNode = function(headA, headB) {
let a = headA;
let b = headB;
if (!a || !b){
return null;
}
while (a !== b){
a = a === null ? headB : a.next;
b = b === null ? headA : b.next;
}
return a;
};
时间复杂度分析
- 双指针遍历:每个指针最多遍历两个链表,总遍历次数为m + n
- 总时间复杂度:O(m + n)
优缺点
- 优点:时间复杂度与哈希表法相同,但空间复杂度仅为O(1),是三种方法中空间效率最高的
- 核心思想:如果两个链表相交,那么它们的相交点到链表末尾的长度是相同的。通过让两个指针分别遍历两个链表,当一个指针到达末尾时,将其指向另一个链表的头节点,这样两个指针最终会在相交点相遇。
三种方法的比较
| 方法 | 时间复杂度 | 空间复杂度 | 优点 | 缺点 |
|---|---|---|---|---|
| 暴力法 | O(m×n) | O(1) | 实现简单 | 效率低,不适用于长链表 |
| 哈希表法 | O(m+n) | O(m) | 实现简单,效率高 | 需要额外空间 |
| 双指针法 | O(m+n) | O(1) | 时间空间效率都高 | 逻辑较难理解 |
为什么双指针法是最佳选择?
双指针法之所以是最佳解法,是因为它在时间复杂度与哈希表法相同(O(m+n))的情况下,空间复杂度却低得多(O(1) vs O(m))。在实际应用中,尤其是处理大规模数据时,空间效率尤为重要。
双指针法的原理详解
假设链表A的长度为m,链表B的长度为n,相交点到链表末尾的长度为k。
- 如果m > n,那么链表A的指针a会比链表B的指针b多走(m-n)步
- 当a到达链表A末尾时,a指向链表B的头节点
- 当b到达链表B末尾时,b指向链表A的头节点
- 此时,a和b到相交点的距离相同,因此它们会同时到达相交点
这个方法的巧妙之处在于,它通过"绕圈"的方式,让两个指针在相交点相遇,而不需要额外的空间。
总结
- 暴力法:适合理解问题,但实际应用中应避免使用
- 哈希表法:实现简单,时间效率高,但需要额外空间
- 双指针法:时间空间效率都最优,是LeetCode 160题的最佳解法
在实际面试中,双指针法是面试官最希望看到的解法,因为它展示了对问题的深入理解和空间效率的考虑。
希望这个详细解析对你有所帮助!在刷题过程中,理解每种解法的优缺点,选择最适合的解决方案,是提升编程能力的关键。