您的位置:首页 > Web前端

[剑指offer学习心得]之:两个链表的第一个公共结点

2016-10-05 19:18 441 查看
题目:输入两个链表,找出它们的第一个公共结点。

看到这道题,第一个想到的自然是暴力法:第一链表上顺序遍历每个结点,每遍历到一个结点,在第二个链表上顺序遍历每个结点。如果在第二个链表上有一个结点和第一个链表上结点相同,说明两个链表在这个结点上重合,于是找到了它们的公共结点。如果一个长度为m,一个长度为n,时间复杂度就是O(mn)

厉害了word哥,找个公共结点花了这么长时间,有没有其他办法?

其实还是有的,我们分析一下有公共结点的两个链表的特点就知道了。这两个链表从某一个结点开始,它们的next都指向同一个结点,所以从第一个公共结点开始,之后它们所有的结点都是重合的不可能出现分叉。

所以我们可以发现,如果两个链表有公共结点,那么公告结点出现在两个链表尾部,如果从后面往前比较,最后一个相同的不就是我们要找的第一个公告结点了嘛?从后往前,可以用到栈的方法,分别把两个链表结点放入两个栈里,这样两个链表的尾结点位于两个栈的栈顶,接下来比较两个栈顶的结点是否相同就可以了。

但是上面思路需要两个辅助的栈,是要耗费空间的。空间复杂度为O(m+n)。所以还有第三个思路:

首先遍历两个链表得到它们的长度,就知道哪个链表比较长,以及长的链表比短的链表多几个结点。第二次遍历时,较长的链表先走若干步,接着同时在两个链表上遍历,找到的第一个相同的结点就是它们的第一个公共结点。

第三种思路和第二种思路相比,时间复杂度相同,但是不需要辅助的栈,提高了空间效率,当然是更好的。

所以可以考虑代码了。

注意,在此之前,当然是要准备好测试用例:

功能测试(两个链表有公共结点:第一个公共结点在链表的中间、末尾、头部;两个链表没有公共结点)

特殊输入测试(输入的链表头结点为null)

代码实现:

public class FindFirstCommonNode {

public static void main(String[] args) {
test1();
test2();
test3();
test4();
}
private static void test1() {
// 第一个公共结点在链表中间
// 1 - 2 - 3 \
//            6 - 7
//     4 - 5 /
ListNode n1 = new ListNode(1);
ListNode n2 = new ListNode(2);
ListNode n3 = new ListNode(3);
ListNode n4 = new ListNode(4);
ListNode n5 = new ListNode(5);
ListNode n6 = new ListNode(6);
ListNode n7 = new ListNode(7);
n1.next = n2;
n2.next = n3;
n3.next = n6;
n6.next = n7;
n4.next = n5;
n5.next = n6;
System.out.println(findFirstCommonNode(n1, n4)); // 6
}
private static void test2() {
// 没有公共结点
// 1 - 2 - 3 - 4
//
// 5 - 6 - 7
ListNode n1 = new ListNode(1);
ListNode n2 = new ListNode(2);
ListNode n3 = new ListNode(3);
ListNode n4 = new ListNode(4);
ListNode n5 = new ListNode(5);
ListNode n6 = new ListNode(6);
ListNode n7 = new ListNode(7);
n1.next = n2;
n2.next = n3;
n3.next = n4;
n5.next = n6;
n6.next = n7;
System.out.println(findFirstCommonNode(n1, n5)); // null
}
private static void test3() {
// 公共结点是最后一个结点
// 1 - 2 - 3 - 4 \
//                7
//         5 - 6 /
ListNode n1 = new ListNode(1);
ListNode n2 = new ListNode(2);
ListNode n3 = new ListNode(3);
ListNode n4 = new ListNode(4);
ListNode n5 = new ListNode(5);
ListNode n6 = new ListNode(6);
ListNode n7 = new ListNode(7);
n1.next = n2;
n2.next = n3;
n3.next = n4;
n4.next = n7;
n5.next = n6;
n6.next = n7;
System.out.println(findFirstCommonNode(n1, n5)); // 7
}
private static void test4() {
// 公共结点是第一个结点
// 1 - 2 - 3 - 4 - 5
// 两个链表完全重合
ListNode n1 = new ListNode(1);
ListNode n2 = new ListNode(2);
ListNode n3 = new ListNode(3);
ListNode n4 = new ListNode(4);
ListNode n5 = new ListNode(5);
ListNode n6 = new ListNode(6);
ListNode n7 = new ListNode(7);
n1.next = n2;
n2.next = n3;
n3.next = n4;
n4.next = n5;
System.out.println(findFirstCommonNode(n1, n1)); // 1
}

/**
* 链表结点类
*/
private static class ListNode {
int val;
ListNode next;
public ListNode() {
}
public ListNode(int val) {
this.val = val;
}
@Override
public String toString() {
return val + "";
}
}

/**
*
* @param head1 第一个链表
* @param head2 第二个链表
* @return 找到的公共结点,没有返回null
*/
private static ListNode findFirstCommonNode(ListNode head1,ListNode head2){
//首先得到两个链表的长度
int length1=getListLength(head1);
int length2=getListLength(head2);
int dif=length1-length2;

ListNode longListHead=head1;
ListNode shortListHead=head2;
if(dif<0){
longListHead=head2;
shortListHead=head1;
dif=length2-length1;
}

//长链表先走几步
for(int i=0;i<dif;i++){
longListHead=longListHead.next;
}

while((longListHead!=null)&&(shortListHead!=null)&&(longListHead!=shortListHead)){
longListHead=longListHead.next;
shortListHead=shortListHead.next;
}

//得到第一个公共结点并返回
return longListHead;
}

/**
* 获取链表的长度
* @param head
* @return
*/
private static int getListLength(ListNode head){
int result=0;
while(head!=null){
result++;
head=head.next;
}
return result;
}
}


结果:

6

null

7

1
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  链表 遍历