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:(栈方法)
思路分析
若两个链表存在公共结点,那么从第一个重合结点开始,往后所以结点都是重合的,因此可以从两个链表尾部开始遍历比较它们的结点,即最后一个相等结点就是第一个重合结点
由于单向链表只能从头结点开始遍历,而我们要的是从尾结点开始,因此可以使用栈来存放链表结点(先进后出)。在遍历链表时把结点压入栈,最先进的是头结点最后出,最晚进的是尾结点最先出。
- 定义两个栈,存放两个链表结点
- 遍历两个链表,遍历过程中把结点压入栈
- 声明一个新结点,初始为null
- 用一个循环依次把两个栈的栈顶元素弹出并赋值给新结点,循环终结条件为两个栈为空且栈顶元素不相等
- 当循环终止时,新结点存放的是最后一次循环栈弹出的结点,此结点就是两链表第一个重合结点
实现代码
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(差值法)
思路分析
若两个链表存在公共结点,下面通过两链表长度相等和不相等进行讨论
当两链表长度相等时,以相同的速度同时遍历两链表,则两链表会同时到达尾结点(可以理解为同一起点出发),在遍历过程若两结点相等,此结点就是第一个重合结点
当两链表长度不相等时,以相同的速度同时遍历两链表,则短的链表会先到达尾结点(不是从同一起点出发),此时不能通过两结点是否相等来判断是否是第一个重合结点
综上,要想使遍历过程中两结点相等的结点就是第一个重合结点,需要让两链表从同一起点出发,方法如下
算出两链表的长度,并作长度差,先让长的链表遍历长度差的值,之后让两链表以相同的速度同时遍历,此时它们就是从同一起点出发
步骤
- 遍历两个链表得到它们的长度,并让两个长度相减得到长度差delta
- 定义一个指针p1初始化为较长的链表的头节点,p1先遍历delta步
- 再定义一个指针p2初始化为较短的链表的头节点
- 使这两个指针按照相同的速度遍历,当它们相遇时,相遇的节点就是第一个重合的节点
代码实现
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. 排序的循环链表