算法通关村第 1 关 | 链表高频算法题——两个链表的第一个公共节点

Leetcode链接:剑指 Offer 52. 两个链表的第一个公共节点 - 力扣(LeetCode)

看到题目没有思路时首先考虑常用数据结构和算法思想。

常用数据结构:数组、链表、队列、栈、Map、Set、二叉树、堆等。

常用算法思想:查找、排序、双指针、递归、迭代、分支、贪心、回溯、动态规划等。

1.使用栈

算法通关村第 1 关 | 链表高频算法题——两个链表的第一个公共节点_第1张图片

思路:分别遍历两个链表,并将节点存入到栈中。两个栈同时依次比较顶元素是否相等,如果相等,用一个变量保存最新相等的节点(相等的节点就是公共节点),分别弹出顶元素,直到不相等的节点(不相等的节点就不是公共节点);如果不相等,说明不是公共节点,返回最新相等的节点,即第一个公共节点。

 算法通关村第 1 关 | 链表高频算法题——两个链表的第一个公共节点_第2张图片

 Java代码:

/**
 * Definition for singly-linked list.
 * public class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode(int x) {
 *         val = x;
 *         next = null;
 *     }
 * }
 */
class Solution {
    ListNode getIntersectionNode(ListNode headA, ListNode headB) {
        // 使用栈
        Stack stackA = new Stack();
        Stack stackB = new Stack();
        // 遍历链表A,将节点存入栈A中
        ListNode cur = headA;
        while(cur != null){
            stackA.push(cur);
            cur = cur.next;
        }
        // 遍历链表B,将节点存入栈B中
        cur = headB;
        while(cur != null){
            stackB.push(cur);
            cur = cur.next;
        }

        // 依次比较栈A、栈B元素
        ListNode res = null;
        while(stackA.size() > 0 && stackB.size() > 0){
            if(stackA.peek() == stackB.peek()){
                res = stackA.pop();
                stackB.pop();
            }else{
                break;
            }
        }
        // 没有找到公共节点
        return res;
    }
}

用此方法,空间复杂度O(n),时间复杂度O(n)。

2.Set

思路:首先遍历一个链表,并将节点存入Set,然后遍历另一个链表,同时判断节点是否存在于Set,如果存在则说明找到第一个公共节点,可直接返回,如果都不存在,则说明无公共节点,返回null。

Java代码:

/**
 * Definition for singly-linked list.
 * public class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode(int x) {
 *         val = x;
 *         next = null;
 *     }
 * }
 */
class Solution {
    ListNode getIntersectionNode(ListNode headA, ListNode headB) {
        // 使用Set
        Set set = new HashSet();
        // 首先遍历链表A,并将节点存入到Set中
        ListNode cur = headA;
        while(cur != null){
            set.add(cur);
            cur = cur.next;
        }
        // 然后遍历链表B,判断链表是否存在
        cur = headB;
        while(cur != null){
            if(set.contains(cur)){
                // 找到第一个公共节点
                return cur;
            }

            cur = cur.next;
        }
        // 没有找到公共节点
        return null;
    }
}

用此方法,空间复杂度O(n),时间复杂度O(n)。

3.两个链表拼接

A:0-1-2-3-4-5

B:a-b-4-5

AB:0-1-2-3-4-5-a-b-4-5

BA:a-b-4-5-0-1-2-3-4-5

可以看到使用A、B拼接后,后面的4-5就是公共链表,4就是第一个公共节点。道理是什么?从几何角度分析。

算法通关村第 1 关 | 链表高频算法题——两个链表的第一个公共节点_第3张图片

如果链表A、B存在公共节点,则以公共节点为分界点将两个链表分别分为left、right两部分,right_a与right_b相等,所以通过拼接,left_a+right_a+left_b与left_b+right_b+left_a相等,所以left_a+right_b+left_b后就是第一个公共节点。

可以通过新建两个链表,用两个指针同时遍历来完成要求,但是为了节省空间,可以使用双指针,在遍历完一个链表后,从另一个链表头开始遍历,达到相同的效果。

Java代码:

/**
 * Definition for singly-linked list.
 * public class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode(int x) {
 *         val = x;
 *         next = null;
 *     }
 * }
 */
class Solution {
    ListNode getIntersectionNode(ListNode headA, ListNode headB) {
        if(headA == null || headB == null){
            return null;
        }
        // 使用链表拼接法
        ListNode curA = headA;//先遍历链表A的指针
        ListNode curB = headB;//先遍历链表B的指针

        //只要curA和curB不同时到达结尾
        while(!(curA == null && curB == null)){
            if(curA == curB){
                // 找到公共节点
                return curA;
            }

            //如果A到达链表结尾,从B开始
            if(curA == null){
                curA = headB;
                curB = curB.next;
            }
            // 如果B到达链表结尾,从A开始
            else if(curB == null){
                curB = headA;
                curA = curA.next;
            }
            else{
                curA = curA.next;
                curB = curB.next;
            }
            
        }

        // 没有找到公共节点
        return null;
    }
}

用此方法,空间复杂度为O(1),时间复杂度为O(n)。

4.差和双指针

思路:先遍历两个链表得到两个链表的长度,然后计算两个链表长度之差| lenA - lenB |,让长度大的链表先走| lenA - lenB |步,然后再一起遍历并判断是否相等,如果相等,则说明找到第一个公共节点返回,如果都不相等,返回null。

算法通关村第 1 关 | 链表高频算法题——两个链表的第一个公共节点_第4张图片

Java代码:

/**
 * Definition for singly-linked list.
 * public class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode(int x) {
 *         val = x;
 *         next = null;
 *     }
 * }
 */
class Solution {
    ListNode getIntersectionNode(ListNode headA, ListNode headB) {
        if(headA == null || headB == null){
            return null;
        }
        // 使用差和双指针法
        ListNode curA = headA;//遍历链表A的指针
        int lenA = 0;
        ListNode curB = headB;//遍历链表B的指针
        int lenB = 0;
        //统计链表A的长度
        while(curA != null){
            lenA++;
            curA = curA.next;
        }
        //统计链表B的长度
        while(curB != null){
            lenB++;
            curB = curB.next;
        }


        curA = headA;
        curB = headB;
        if(lenA > lenB){
            // 链表A走 (lenA - lenB) 步
            for(int i = 0; i < (lenA - lenB); i++){
                curA = curA.next;
            }
        }else{
            // 链表B走 (lenB - lenA) 步
            for(int i = 0; i < (lenB - lenA); i++){
                curB = curB.next;
            }

        }

        // 同时遍历链表A和链表B
        while(curA != curB){
            curA = curA.next;
            curB = curB.next;
        }

        // 要么是第一个公共节点,要么是null
        return curA;
    }
}

用此方法,空间复杂度O(1),时间复杂度O(n)。

你可能感兴趣的:(链表,算法,链表,数据结构)