在日常的算法面试中,链表相关的问题总是频繁出现。而链表相交问题更是其中的经典之一。想象一下,你面前有两条链表,它们在某一点相交,然后合并成一条链表。如何高效地找到这个交点?今天,我们将通过Java和C++两种语言的实现,深入探讨这个问题的解决思路,并揭示其中的巧妙之处。
1. 问题描述
给定两个单链表 headA 和 headB,判断它们是否相交,并返回相交的节点。如果两个链表没有交点,则返回 null。
注意:
- 链表相交指的是两个链表在某个节点之后完全重合,而不是仅仅值相同。
- 链表的结构在相交之后必须完全一致。
2. 直观解法:暴力遍历
最直观的解法是使用双重循环,遍历链表A的每一个节点,同时遍历链表B的每一个节点,判断是否有相同的节点。这种方法的时间复杂度为O(m*n),其中m和n分别是链表A和链表B的长度。显然,这种方法的效率较低,尤其是在链表较长时。
3. 优化思路:双指针法
为了优化时间复杂度,我们可以使用双指针法。这种方法的核心思想是让两个指针分别遍历两个链表,当其中一个指针到达链表末尾时,将其指向另一个链表的头节点。这样,两个指针最终会在相交节点相遇,或者同时到达链表末尾(表示没有交点)。
java代码
public ListNode getIntersectionNode(ListNode headA, ListNode headB) {
if (headA == null || headB == null) {
return null;
}
ListNode ha = headA;
ListNode hb = headB;
while (ha != hb) {
ha = ha != null ? ha.next : headB;
hb = hb != null ? hb.next : headA;
}
return ha;
}
C++代码
ListNode* getIntersectionNode(ListNode* headA, ListNode* headB) {
if (headA == nullptr || headB == nullptr) {
return nullptr;
}
ListNode* ha = headA;
ListNode* hb = headB;
while (ha != hb) {
ha = ha != nullptr ? ha->next : headB;
hb = hb != nullptr ? hb->next : headA;
}
return ha;
}
4. 为什么双指针法有效?
双指针法的关键在于消除两个链表的长度差。假设链表A的长度为m,链表B的长度为n,且它们的相交部分长度为k。当两个指针分别遍历完自己的链表后,切换到另一个链表的头节点,相当于让两个指针都走了m + n - k步。这样,它们最终会在相交节点相遇。
举例说明:
- 链表A:
1 -> 2 -> 3 -> 4 -> 5 - 链表B:
6 -> 7 -> 4 -> 5 - 相交节点为
4。
指针ha的遍历路径:1 -> 2 -> 3 -> 4 -> 5 -> 6 -> 7 -> 4
指针hb的遍历路径:6 -> 7 -> 4 -> 5 -> 1 -> 2 -> 3 -> 4
最终,两个指针在节点 4 处相遇。
5. 时间复杂度与空间复杂度
- 时间复杂度:O(m + n),其中m和n分别是链表A和链表B的长度。两个指针最多遍历m + n个节点。
- 空间复杂度:O(1),只使用了常数级别的额外空间。
6. 总结
链表相交问题是一个经典的算法问题,通过双指针法,我们可以高效地找到两个链表的交点。这种方法不仅时间复杂度低,而且代码简洁易懂。理解其背后的原理,可以帮助我们在面对类似问题时,快速找到解决方案。
在未来的学习中,我们将继续探索更多有趣的算法问题,挑战自我,不断提升。希望今天的分享能为你带来新的启发,也欢迎你在评论区分享你的想法和疑问。
坚持打卡,持续进步!希望友友们监督哈!