Java每日一题——>剑指 Offer 52. 两个链表的第一个公共节点

Posted stormzhuo

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Java每日一题——>剑指 Offer 52. 两个链表的第一个公共节点相关的知识,希望对你有一定的参考价值。

这是LeetCode上的 [52,两个链表的第一个公共节点],难度为 [简单]

题目

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

如下面的两个链表:

在节点 c1 开始相交。

题解1:(蛮方法)

在第1个链表中顺序遍历每个结点,每遍历到一个结点时,在2个链表中顺序遍历每一个结点(即嵌套循环),若两个结点相等,则此结点就是两个链表的第一个公共结点

代码实现

public class Solution 

    public static ListNode getIntersectionNode1(ListNode headA, ListNode headB) 
         // 外层循环,遍历第一个链表,每遍历一个结点,内层循环会遍历一遍第二个链表
        while (headA != null) 
            // 声明一个新节点,初始化为第二个链表头结点
            ListNode nB = headB;
           /* 遍历第二链表,用第一个链表的结点依次和第二链表结点对比
              若相等则此结点就是第一个重合结点*/
            while (nB != null) 
                if (headA == nB) 
                    return nB;
                
                nB = nB.next;
            
            headA = headA.next;
        
        return null;
    

结点类

public class ListNode<T> 

    T val;
    ListNode next;

    public ListNode() 

    

    public ListNode(T val) 
        this.val = val;
    

    public ListNode(T val, ListNode next) 
        this.next = next;
    

测试代码

public class Test 

    @org.junit.Test
    public void test1() 
        ListNode<String> head1 = new ListNode<>("a1");
        ListNode<String> one = new ListNode<>("a2");
        ListNode<String> two = new ListNode<>("c1");
        ListNode<String> three = new ListNode<>("c2");
        ListNode<String> four = new ListNode<>("c3");
        head1.next = one;
        one.next = two;
        two.next = three;
        three.next = four;

        ListNode<String> head2 = new ListNode<>("b1");
        ListNode<String> first = new ListNode<>("b2");
        ListNode<String> second = new ListNode<>("b3");
        head2.next = first;
        first.next = second;
        second.next = two;

        ListNode intersectionNode = Solution.getIntersectionNode1(head1, head2);
        System.out.println("两链表中的重合结点的值: " + intersectionNode.val);
    
 

复杂度分析

假设两链表长度为n,m

时间复杂度:嵌套遍历两个链表,则时间复杂度为O(nm);

空间复杂度:只声明了一个新结点,则空间复杂度为O(1);

题解2:(栈方法)

思路分析

若两个链表存在公共结点,那么从第一个重合结点开始,往后所以结点都是重合的,因此可以从两个链表尾部开始遍历比较它们的结点,即最后一个相等结点就是第一个重合结点

由于单向链表只能从头结点开始遍历,而我们要的是从尾结点开始,因此可以使用栈来存放链表结点(先进后出)。在遍历链表时把结点压入栈,最先进的是头结点最后出,最晚进的是尾结点最先出。

  1. 定义两个栈,存放两个链表结点
  2. 遍历两个链表,遍历过程中把结点压入栈
  3. 声明一个新结点,初始为null
  4. 用一个循环依次把两个栈的栈顶元素弹出并赋值给新结点,循环终结条件为两个栈为空且栈顶元素不相等
  5. 当循环终止时,新结点存放的是最后一次循环栈弹出的结点,此结点就是两链表第一个重合结点

实现代码

public class Solution 

    public static ListNode getIntersectionNode2(ListNode headA, ListNode headB) 
    	// 声明两个栈,存放两个链表的结点
        Stack<Object> stack1 = new Stack<>();
        Stack<Object> stack2 = new Stack<>();
        // 遍历链表A,遍历过程中把结点压入栈
        while (headA != null) 
            stack1.push(headA);
            headA = headA.next;
        
        // 遍历链表B,遍历过程中把结点压入栈
        while (headB != null) 
            stack2.push(headB);
            headB = headB.next;
        
        // 声明新结点,用于存放重合结点
        ListNode n = null;
        /* 若两个栈不为空且栈顶元素相等(结点相同),则循环继续,否则循环终止
        *  那么最后一次循环出栈结点即为两个链表的第一个重合结点*/
        while (!stack1.isEmpty() && !stack2.isEmpty() && stack1.peek() == stack2.peek()) 
            // 每次循环把出栈结点赋给n,最后一次循环n即为第一个重合结点
            n = (ListNode) stack1.pop();
            stack2.pop();
        
        return n;
    

复杂度分析

假设两链表长度为n,m

时间复杂度:需要遍历两个链表,则时间复杂度为O(n+m);

空间复杂度:需要两个栈来存放两链表结点,则空间复杂度为O(n+m);

和蛮力法相比,这是一种用空间换时间的方法

题解3:(环入口方法)

思路分析

若两个链表存在公共结点,使其中一个链表的尾结点指向它的头结点,此时两链表会构成一个环形链表,则环的入口结点就是两链表的第一个重合结点,环的入口结点在以往文章以给出链表中环的入口节点,这里不再阐述

代码实现

public class Solution 

    public static ListNode getIntersectionNode3(ListNode headA, ListNode headB) 
        // 声明一个结点,存放链表A的头结点
        ListNode nA = headA;
        // 判断链表A是否null,若null则没有重合结点返回null
        if (nA == null) 
            return null;
        
        // 遍历链表A到尾结点
        while (nA.next != null) 
            nA = nA.next;
        
        // 让尾结点指向链表A的头结点,构成一个环形链表
        nA.next = headA;
        // 定义两个结点(快慢指针),初始为链表B头结点
        ListNode fast = headB;
        ListNode slow = headB;
         /* 遍历链表B,slow每次移动1步,fast每次移动2步
          循环终止条件为fast不为null且fast下一个结点也不为null
          防止fast.next和fast.next.next报空指针异常*/
        while (fast != null && fast.next != null) 
            fast = fast.next.next;
            slow = slow.next;
            // 若fast等于slow,即两个结点相遇,则存在环
            if (fast == slow) 
                // 快结点赋值为链表B头结点
                fast = headB;
                /* 快结点fast和慢结点slow以相同的速度遍历链表B,终止条件尾两结点相同,
                   相同结点即为链表第一个重合结点*/
                while (fast != slow) 
                    fast = fast.next;
                    slow = slow.next;
                
                // 返回相同结点之前,需要恢复原有链表结构,即链表A尾结点不再指向A的头结点,而是为null
                nA.next = null;
                return slow;
            
        
        nA.next = null;
        return null;
    

复杂度分析

假设两链表长度为n,m

时间复杂度:在最初找其中一个链表尾结点时,需要遍历n或m此,故O(n)或O(m),当链表构成环时,在找相遇结点时,走过的距离不会超过链表总长度n+m(可以理解为循环的次数不会超过n+m),在找环入口结点时也一样。故总的时间复杂度为O(n)或O(m) + O(n+m) + O(n+m) = On(n+m)

空间复杂度:我们只使用了nA,fast,slow 三个结点。空间复杂度为O(1);

题解4(差值法)

思路分析

若两个链表存在公共结点,下面通过两链表长度相等和不相等进行讨论

当两链表长度相等时,以相同的速度同时遍历两链表,则两链表会同时到达尾结点(可以理解为同一起点出发),在遍历过程若两结点相等,此结点就是第一个重合结点

当两链表长度不相等时,以相同的速度同时遍历两链表,则短的链表会先到达尾结点(不是从同一起点出发),此时不能通过两结点是否相等来判断是否是第一个重合结点

综上,要想使遍历过程中两结点相等的结点就是第一个重合结点,需要让两链表从同一起点出发,方法如下

算出两链表的长度,并作长度差,先让长的链表遍历长度差的值,之后让两链表以相同的速度同时遍历,此时它们就是从同一起点出发

步骤

  1. 遍历两个链表得到它们的长度,并让两个长度相减得到长度差delta
  2. 定义一个指针p1初始化为较长的链表的头节点,p1先遍历delta步
  3. 再定义一个指针p2初始化为较短的链表的头节点
  4. 使这两个指针按照相同的速度遍历,当它们相遇时,相遇的节点就是第一个重合的节点

代码实现

public class Solution 
    public ListNode getIntersectionNode(ListNode headA, ListNode headB) 
        // 获取两个链表的长度
        int lenA = getLength(headA);
        int lenB = getLength(headB);
        // 求两个链表的差值
        int delta = Math.abs(lenA - lenB);
        // 把长的链表的头结点赋给front结点,短的头结点赋给back结点
        ListNode front = lenA > lenB ? headA : headB;
        ListNode back = lenA > lenB ? headB : headA;
        // front结点先遍历delta步
        for (int i = 0; i < delta; i++) 
            front = front.next;
        
        /* front,back以相同的速度遍历,front从delta开始,back从头结点开始
        * 当front=back时,两结点相遇,终止循环,相遇结点即为两链表重合结点*/
        while (front != back) 
            front = front.next;
            back = back.next;
        
        return back;
    

    public int getLength(ListNode head) 
        // 链表长度,初始为0
        int length = 0;
        // 遍历链表,每次循环length+1
        while (head != null) 
            length++;
            head = head.next;
        
        return length;
    

复杂度分析

假设两链表长度为n,m

时间复杂度:需要遍历两个链表,则时间复杂度为O(n+m);

空间复杂度:声明了两个结点,则空间复杂度为O(1);

以上是关于Java每日一题——>剑指 Offer 52. 两个链表的第一个公共节点的主要内容,如果未能解决你的问题,请参考以下文章

乱序版 ● 剑指offer每日算法题打卡题解—— 双指针(题号18,22,25,52)

Java每日一题——>剑指 Offer II 027. 回文链表

Java每日一题——>剑指 Offer II 029. 排序的循环链表

Java每日一题——>剑指 Offer II 035. 最小时间差(三解,蛮力,排序,哈希)

每日一题 - 剑指 Offer 61. 扑克牌中的顺子

Java每日一题——> 剑指 Offer II 028. 展平多级双向链表