算法通关村第一关——链表经典问题之两个链表第一个公共子节点笔记

问题:剑指 Offer 52. 两个链表的第一个公共节点

输入两个链表,找出它们的第一个公共节点。
如下面的两个链表:

在节点 c1 开始相交。

题目来源:剑指 Offer 52. 两个链表的第一个公共节点

解答一:暴力枚举

解答思路

依次遍历链表 A 和链表 B 中的每个节点,直到找到相同的节点。

Java 代码实现

/**
 * 暴力枚举
 *
 * @param headA 链表 A 的首节点
 * @param headB 链表 B 的首节点
 * @return 两个链表的第一个公共节点
 */
public static ListNode getIntersectionNode1(ListNode headA, ListNode headB) {
    if (headA == null || headB == null) {
        return null;
    }

    for (ListNode nodeA = headA; nodeA != null; nodeA = nodeA.next) {
        for (ListNode nodeB = headB; nodeB != null; nodeB = nodeB.next) {
            if (nodeA == nodeB) {
                return nodeA;
            }
        }
    }

    return null;
}

时间和空间复杂度

时间复杂度: O ( m ∗ n ) O(m*n) O(mn)
空间复杂度: O ( 1 ) O(1) O(1)

解答二:使用集合

解答思路

将链表 A 中的所有节点放入到一个集合 A 中,依次遍历链表 B 中的每个节点,并判断当前节点是否在集合 A 中。

Java 代码实现

/**
 * 使用集合
 *
 * @param headA 链表 A 的首节点
 * @param headB 链表 B 的首节点
 * @return 两个链表的第一个公共节点
 */
public static ListNode getIntersectionNode2(ListNode headA, ListNode headB) {
    if (headA == null || headB == null) {
        return null;
    }

    HashSet<ListNode> nodeHashSetA = new HashSet<>();
    ListNode nodeA = headA;
    while (nodeA != null) {
        nodeHashSetA.add(nodeA);
        nodeA = nodeA.next;
    }

    ListNode nodeB = headB;
    while (nodeB != null) {
        if (nodeHashSetA.contains(nodeB)) {
            return nodeB;
        }
        nodeB = nodeB.next;
    }

    return null;
}

时间和空间复杂度

时间复杂度: O ( m + n ) O(m+n) O(m+n)
空间复杂度: O ( m ) O(m) O(m)

解答三:使用栈

解答思路

先将链表 A 中的所有元素放入栈 A 中,链表 B 中的所有元素放入栈 B 中,然后让栈 A 和 栈 B 中的元素依次出栈,最后一个出栈的共同元素即是第一个公共子节点。该方法相当于从后往前找两个链表的第一个公共子节点。
该方法的执行流程示例如下图所示:
在这里插入图片描述

在这里插入图片描述

Java 代码实现

/**
 * 使用栈
 *
 * @param headA 链表 A 的首节点
 * @param headB 链表 B 的首节点
 * @return 两个链表的第一个公共节点
 */
public static ListNode getIntersectionNode3(ListNode headA, ListNode headB) {
    if (headA == null || headB == null) {
        return null;
    }

    Stack<ListNode> stackA = new Stack<>();
    Stack<ListNode> stackB = new Stack<>();
    ListNode nodeA = headA;
    while (nodeA != null) {
        stackA.push(nodeA);
        nodeA = nodeA.next;
    }
    ListNode nodeB = headB;
    while (nodeB != null) {
        stackB.push(nodeB);
        nodeB = nodeB.next;
    }

    ListNode intersectionNode = null;
    while (stackA.size() > 0 && stackB.size() > 0) {
        if (stackA.peek() == stackB.peek()) {
            intersectionNode = stackA.pop();
            stackB.pop();
        } else {
            break;
        }
    }

    return intersectionNode;
}

时间和空间复杂度

时间复杂度: O ( m + n ) O(m+n) O(m+n)
空间复杂度: O ( m + n ) O(m+n) O(m+n)

解答四:双指针

解答思路

可以将链表分为左右两个部分,则右边部分为两个链表的公共节点,如下图所示:
在这里插入图片描述
可以发现如果链表 A 后面链结链表 B,链表 B 后面链结链表 A,则最后一部分链表 A 和链表 B 中的节点是一样的,一样部分的第一个节点便是要找的两个链表的第一个公共子节点,也就是说分别遍历链表A-B 和链表 B-A 则遍历到某个位置时就能找到相交的点。
按照这个思路我们其实可以在每个链表访问完以后,再调整到链表的表头继续遍历,这样可以不用新建链表。
该方法的执行流程示例如下图所示:
在这里插入图片描述

Java 代码实现

/**
 * 双指针
 *
 * @param headA 链表 A 的首节点
 * @param headB 链表 B 的首节点
 * @return 两个链表的第一个公共节点
 */
public static ListNode getIntersectionNode4(ListNode headA, ListNode headB) {
    if (headA == null || headB == null) {
        return null;
    }

    ListNode p1 = headA;
    ListNode p2 = headB;
    while (p1 != p2) {
        p1 = p1.next;
        p2 = p2.next;
        if (p1 != p2) {
            if (p1 == null) {
                p1 = headB;
            }
            if (p2 == null) {
                p2 = headA;
            }
        }
    }

    return p1;
}

注意:如果 p1 = p1.next; p2 = p2.next;后没有先判断 p1 != p2 再去判断p1 == null 和 p2 == null 的话可能会导致死循环。
该示例如下图所示:在这里插入图片描述
从图中可以看到在这种情况下,会出现 p1 == p2 == null 然后 p1 和 p2 都改变了指向重新回头反复遍历的问题。

时间和空间复杂度

时间复杂度: O ( m + n ) O(m+n) O(m+n)
空间复杂度: O ( 1 ) O(1) O(1)

解答五:差和双指针

解答思路

首先求出链表 A 的长度 lengthA 和链表 B 的长度 lengthB,然后让长度长的链表先走 ∣ l e n g t h A − l e n g t h B ∣ \left | lengthA-lengthB \right | lengthAlengthB步,最后再同时遍历两个链表,找到的第一个相同结点便是两个链表的第一个公共子节点。
该方法的执行流程示例如下图所示:
在这里插入图片描述

Java 代码实现

/**
 * 差和双指针
 *
 * @param headA 链表 A 的首节点
 * @param headB 链表 B 的首节点
 * @return 两个链表的第一个公共节点
 */
public static ListNode getIntersectionNode5(ListNode headA, ListNode headB) {
    if (headA == null || headB == null) {
        return null;
    }

    // 计算链表 A 的长度
    ListNode nodeA = headA;
    int lengthA = 0;
    while (nodeA != null) {
        lengthA++;
        nodeA = nodeA.next;
    }

    // 计算链表 B 的长度
    ListNode nodeB = headB;
    int lengthB = 0;
    while (nodeB != null) {
        lengthB++;
        nodeB = nodeB.next;
    }

    // 计算链表 A 和链表 B 的长度差
    int sub = lengthA > lengthB ? lengthA - lengthB : lengthB - lengthA;

    ListNode p1 = headA;
    ListNode p2 = headB;

    // 长的先走 sub 步
    if (lengthA > lengthB) {
        int count = 0;
        while (count < sub) {
            p1 = p1.next;
            count++;
        }
    }
    if (lengthB > lengthA) {
        int count = 0;
        while (count < sub) {
            p2 = p2.next;
            count++;
        }
    }

    // 同时遍历两个链表
    while (p1 != p2) {
        p1 = p1.next;
        p2 = p2.next;
    }

    return p1;
}

时间和空间复杂度

时间复杂度: O ( m + n ) O(m+n) O(m+n)
空间复杂度: O ( 1 ) O(1) O(1)

总结

针对两个链表第一个公共子节点问题,本文给出了 5 种解答思路和相应的代码实现。其实无论是哪一种思路,都涉及到了遍历链表中的元素,而不同的方法选用了不同的数据结构。

获取本文全部示例代码文件

本文全部示例代码文件可从我的 GitHub 仓库中获取:算法通关村第一关——链表经典问题之两个链表第一个公共子节点笔记代码

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 4
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值