剑指offer_52_两个链表的第一个公共节点【javascript】

70 阅读4分钟

题目链接

leetcode.cn/problems/li…

题目(简单)

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

示例

示例1: 
输入:intersectVal = 8, listA = [4,1,8,4,5], listB = [5,0,1,8,4,5], skipA = 2, skipB = 3
输出:Reference of the node with value = 8
输入解释:相交节点的值为 8 (注意,如果两个列表相交则不能为 0)。从各自的表头开始算起,链表 A 为 [4,1,8,4,5],链表 B 为 [5,0,1,8,4,5]。在 A 中,相交节点前有 2 个节点;在 B 中,相交节点前有 3 个节点。

示例2:
输入:intersectVal = 2, listA = [0,9,1,2,4], listB = [3,2,4], skipA = 3, skipB = 1
输出:Reference of the node with value = 2
输入解释:相交节点的值为 2 (注意,如果两个列表相交则不能为 0)。从各自的表头开始算起,链表 A 为 [0,9,1,2,4],链表 B 为 [3,2,4]。在 A 中,相交节点前有 3 个节点;在 B 中,相交节点前有 1 个节点。

示例3:
输入:intersectVal = 0, listA = [2,6,4], listB = [1,5], skipA = 3, skipB = 2
输出:null
输入解释:从各自的表头开始算起,链表 A 为 [2,6,4],链表 B 为 [1,5]。由于这两个链表不相交,所以 intersectVal 必须为 0,而 skipA 和 skipB 可以是任意值。
解释:这两个链表不相交,因此返回 null

注意:

  • 如果两个链表没有交点,返回 null.
  • 在返回结果后,两个链表仍须保持原有的结构。
  • 可假定整个链表结构中没有循环。
  • 程序尽量满足 O(n) 时间复杂度,且仅用 O(1) 内存。
  • 本题与 160 题相同:leetcode-cn.com/problems/in…

题解

双指针

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

链表 headA 的不相交部分有 a 个节点,链表 headB 的不相交部分有 b 个节点,两个链表相交的部分有 c 个节点, 则有a + c = m,b + c = n;

  • 如果 a = b, 则两个指针会同时到达两个链表相交的节点,此时返回相交的节点;
  • 如果 a ≠ b, 两个指针不会同时到达链表的尾节点,指针pA会移动到链表 headB 的头节点 ,指针pB会移动到链表 headA 的头节点,然后继续移动;在指针 pA 移动了 a+c+b次、指针 pB 移动了 b+c+a 次之后,两个指针会同时到达两个链表相交的节点,该节点也是两个指针第一次同时指向的节点,此时返回相交的节点。
/**
 * Definition for singly-linked list.
 * function ListNode(val) {
 *     this.val = val;
 *     this.next = null;
 * }
 */

/**
 * @param {ListNode} headA
 * @param {ListNode} headB
 * @return {ListNode}
 */
var getIntersectionNode = function(headA, headB) {
    if (headA === null || headB === null) {
        return null;
    }
    let pA = headA, pB = headB;
    while (pA !== pB) {
        pA = pA === null ? headB : pA.next;
        pB = pB === null ? headA : pB.next;
    }
    return pA;
};

遍历

  • 时间复杂度:O(m + n)
  • 空间复杂度:O(1)
/**
 * Definition for singly-linked list.
 * function ListNode(val) {
 *     this.val = val;
 *     this.next = null;
 * }
 */

/**
 * @param {ListNode} headA
 * @param {ListNode} headB
 * @return {ListNode}
 */
var getIntersectionNode = function(headA, headB) {
    const getLength = (head) => {
        let length = 0;
        let node = head;
        while (node !== null) {
            ++length;
            node = node.next;
        }
        return length;
    }
    let aLength = getLength(headA);
    let bLength = getLength(headB);
    let longNode = headA, shortNode = headB;
    let step = aLength - bLength;
    if (bLength > aLength) {
        longNode = headB;
        shortNode = headA;
        step = bLength - aLength;
    }
    for (let i = 0; i < step; i++) {
        longNode = longNode.next;
    }
    while (longNode !== null && shortNode !== null && longNode !== shortNode) {
        longNode = longNode.next;
        shortNode = shortNode.next;
    }
    return longNode;
};

哈希集合

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

思路: 首先遍历链表 headA,并将链表 headA 中的每个节点加入哈希集合中。然后遍历链表 headB,对于遍历到的每个节点,判断该节点是否在哈希集合中:

  • 如果当前节点不在哈希集合中,则继续遍历下一个节点;
  • 如果当前节点在哈希集合中,则后面的节点都在哈希集合中,即从当前节点开始的所有节点都在两个链表的相交部分,因此在链表 headB 中遍历到的第一个在哈希集合中的节点就是两个链表相交的节点,返回该节点。
  • 如果链表 headB 中的所有节点都不在哈希集合中,则两个链表不相交,返回 null。
/**
 * Definition for singly-linked list.
 * function ListNode(val) {
 *     this.val = val;
 *     this.next = null;
 * }
 */

/**
 * @param {ListNode} headA
 * @param {ListNode} headB
 * @return {ListNode}
 */
var getIntersectionNode = function(headA, headB) {
    const visited = new Set();
    let temp = headA;
    while (temp !== null) {
        visited.add(temp);
        temp = temp.next;
    }
    temp = headB;
    while (temp !== null) {
        if (visited.has(temp)) {
            return temp;
        }
        temp = temp.next;
    }
    return null;
};