leetcode题号:剑指 Offer 52.
这道题解法比较多样,而且涉及到几种解链表题目常用的方法。
题目:
题意:需要找到两个链表的 第一个公共节点
比如
nodeA = 1->3->4->5->9
,nodeB = 2->6->5->9
那么这两个链表的 第一个公共节点就是
5->9
,
1.1 Hash
思路:先将
head1链表
元素全部放进Set
集合里,然后一边遍历head2链表
,一边检查Set
中是否有已经存在的节点,调用 contains方法,如果为ture
就代表找到了,返回该节点。
代码:
public static ListNode byHashMap(ListNode head1, ListNode head2) {
if (head1 == null || head2 == null) {
return null;
}
// 保存第一个所有节点的信息
HashSet<ListNode> set = new HashSet<>();
// 先把 head1 的所有数据放进 map
while (head1 != null) {
set.add(head1);
head1 = head1.next;
}
// 将 head2 数据与 head1 对比,看是否包含有
while (head2 != null) {
if (set.contains(head2)) {
return head2;
}
head2 = head2.next;
}
// 执行到最后发现没有公共节点
return null;
}
1.2 使用栈
思路:需要两个栈,分别保存两个链表的信息,链表进栈后,栈顶数据就是链表尾部,正好可以用来判断是否有公共子节点,
还有一点要注意的是 要找到最晚出栈的那一组返回,根据题意要找到最先公共的节点
这种方式需要两个 O(n) 的空间
代码:
public static ListNode byStack(ListNode head1,ListNode head2) {
if (head1 == null || head2 == null) {
return null;
}
// 初始化两个 栈
Stack<ListNode> stack1 = new Stack<>();
Stack<ListNode> stack2 = new Stack<>();
// 将两个链表分别存进两个栈中
while (head1 != null) {
stack1.push(head1);
head1 = head1.next;
}
while (head2 != null) {
stack2.push(head2);
head2 = head2.next;
}
// 用来记录结果
ListNode result = null;
// 对 栈顶进行比较,栈顶也就是链表的尾部
while (stack1.size() > 0 && stack2.size() > 0) {
if (stack1.peek() == stack2.peek()) {
result = stack1.pop();
stack2.pop();
} else {
break;
}
}
return result;
}
1.3 字符拼接法
两个链表进行拼接(比较节点)
A : 1 -> 2 -> 3 -> 4 -> 5 -> 6
B : 6 -> 8 -> 4 -> 5-> 6
把它们分别拼在对方的尾部,此时就变成
A : 1 -> 2 -> 3 -> 4 -> 5 -> 6 ->
6 -> 8 -> 4 -> 5 -> 6
B : 6 -> 8 -> 4 -> 5 -> 6 ->
1-> 2 -> 3 -> 4 -> 5 -> 6
此时它们的长度刚好一致,发现两个链表最后面 从
4
开始,刚好是一样的,这时就能找到第一个公共子节点了
但是这样还是有不行的,因为在源码中是这样 构建 公共字节的点的,看看是如何初始化的。
初始化两个链表
private static ListNode[] initLinkedList() {
ListNode[] heads = new ListNode[2];
// 构造第一个链表交点之前的元素 1 ->2-> 3
heads[0] = new ListNode(1);
ListNode current1 = heads[0];
current1.next = new ListNode(2);
current1 = current1.next;
current1.next = new ListNode(3);
current1 = current1.next;
// 11->22
// 构造第二个链表交点之前的元素
heads[1] = new ListNode(11);
ListNode current2 = heads[1];
current2.next = new ListNode(22);
current2 = current2.next;
// 构造公共交点以及之后的元素
ListNode node4 = new ListNode(4);
current1.next = node4;
current2.next = node4;
ListNode node5 = new ListNode(5);
node4.next = node5;
ListNode node6 = new ListNode(6);
node5.next = node6;
return heads;
}
注意看上面初始化两个链表的源码中, 在最后初始化
node4
节点的时候,current1 和 current2 这两个链表的next
都是指向node4
的,证明它们公共部分的 引用地址是一样的,所以这就是在比较地址
在 deBug 的时候也是发现了这个问题,两个链表 后面公共部分的节点,引用地址都是一样的
此方式实现代码
public static ListNode byCombine(ListNode head1,ListNode head2) {
// 进行判空
if (head1 == null || head2 == null) {
return null;
}
// 分别定义两个节点用来遍历
ListNode p1 = head1;
ListNode p2 = head2;
/**
* 比较两个节点的地址
* 如果相同的话就可以返回了(随便返回哪个,反正都相同)
*/
while (p1 != p2) {
// 不相同则都往后移
p1 = p1.next;
p2 = p2.next;
// 如果两个链表遍历完还是空,则没有交点
if (p1 == null && p2 == null) {
return null;
}
// 如果为空,则指向另一个链表的头节点
if (p1 == null) {
p1 = head2;
}
if (p2 == null) {
p2 = head1;
}
}
return p1;
}
在这个方法中,有一个致命bug,就是修改了当前公共节点的 值,还是会被识别出这是公共子节点,因为这种方式是根据 引用地址比较的。