写在前面
这个题算比较经典的一道题,现在用5种方法来解决这道算法题,全部代码附在文末,包括测试用例,需要自取。
方法1:通过Hash辅助查找
public static ListNode findFirstCommonNodeByMap(ListNode pHead1, ListNode pHead2) {
//warning 忘记判空了
if (pHead1 == null || pHead2 == null) {
return null;
}
Map<ListNode, Integer> map = new HashMap<>();
ListNode p1 = pHead1;
ListNode p2 = pHead2;
//将第一个链表的节点依次放入map
while (p1 != null) {
map.put(p1, null);
p1 = p1.next;
}
while (p2 != null) {
if (map.containsKey(p2)) {
return p2;
}
p2 = p2.next;
}
return null;
}
方法2:通过集合来辅助查找
public static ListNode findFirstCommonNodeBySet(ListNode headA, ListNode headB) {
ListNode h1 = headA;
ListNode h2 = headB;
List<ListNode> list = new ArrayList<>();
while (h1 != null) {
list.add(h1);
h1 = h1.next;
}
while (h2 != null) {
if (list.contains(h2)) {
return h2;
}
h2 = h2.next;
}
return null;
}
方法3:通过栈
public static ListNode findFirstCommonNodeByStack(ListNode headA, ListNode headB) {
Stack<ListNode> h1 = new Stack<>();
Stack<ListNode> h2 = new Stack<>();
ListNode ha = headA;//因为Java是值传递,所以不会改变外部头节点的取值,这2行代码去掉也无妨
ListNode hb = headB;
while (ha != null) {
h1.push(ha);
ha = ha.next;
}
while (hb != null) {
h2.push(hb);
hb = hb.next;
}
// while (!h1.empty()) {
// ListNode temp1 = h1.pop();
// ListNode temp2 = h2.pop();
// if (temp1 == temp2) {
// ListNode temp3 = h1.pop();
// ListNode temp4 = h2.pop();
// if (temp3 != temp4) {
// return temp1;
// }
// }
// }
//优化
//1、用一个变量保存上一个节点
//2、增加判断2个栈大小的逻辑,过滤空栈
// ListNode pre = null;
// while(h1.size() > 0 && h2.size() > 0){
// if(h1.peek() == h2.peek()){
pre = h1.peek();
h1.pop();
h2.pop();
// //优化
// pre = h1.pop();
// h2.pop();
// if(h1.peek() != h2.peek()){
// return pre;
// }
// }else {
// return null;
// }
// }
// return null;
//还能优化
ListNode pre = null;
while (h1.size() > 0 && h2.size() > 0) {
if (h1.peek() == h2.peek()) {
//优化
pre = h1.pop();
h2.pop();
} else {
break;
}
}
return pre;
}
方法4:通过序列拼接
public static ListNode findFirstCommonNodeByCombine(ListNode pHead1, ListNode pHead2) {
//双指针法
//算法思想:如果有A、B,2个链表,拼接成AB,BA,如果AB有公共节点,那肯定是唯一公共节点(由单链表定义可知)
//所以一直遍历到相同的公共节点,就是要找的第一个公共节点
//判空
if (pHead1 == null && pHead2 == null) {
return null;
}
ListNode p1 = pHead1;
ListNode p2 = pHead2;
while (p1 != p2) {
//这里的p1 != p2不能替换成true,否则遇到没有公共节点的情况会死循环
p1 = p1.next;
p2 = p2.next;
if (p1 != p2) {
if (p1 == null) {
p1 = pHead2;
}
if (p2 == null) {
p2 = pHead1;
}
}
}
return p1;
}
方法5:通过差值来实现
public static ListNode findFirstCommonNodeBySub(ListNode pHead1, ListNode pHead2) {
//虽然代码看起来比之前的几种方法多了不少,但思路也比较简单,很巧妙
//算法思想:首先遍历一次,获取2个链表的长度,然后再次遍历,第二次遍历,先让长的链表先走长度差值的距离
//然后2个链表再一起往下走,最后相等的第一个节点,就是要找的节点
//有一种对齐的思想在里面
//判空
if (pHead1 == null && pHead2 == null) {
return null;
}
ListNode p1 = pHead1;
ListNode p2 = pHead2;
int lengthP1 = 0;
int lengthP2 = 0;
//分别获取2个链表的长度
while (p1 != null) {
p1 = p1.next;
lengthP1++;
}
while (p2 != null) {
p2 = p2.next;
lengthP2++;
}
//计算差值,确保结果为绝对值
int sub = lengthP1 > lengthP2 ? lengthP1 - lengthP2 : lengthP2 - lengthP1;
p1 = pHead1;
p2 = pHead2;
//再次进行遍历
if (lengthP1 > lengthP2) {
while (sub > 0) {
p1 = p1.next;
sub--;
}
} else {
while (sub > 0) {
p2 = p2.next;
sub--;
}
}
while (p1 != p2) {
p1 = p1.next;
p2 = p2.next;
}
return p1;
}
全部代码
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Stack;
public class FindFirstCommonNode {
public static void main(String[] args) {
ListNode[] listNodes = initLinkedList();
System.out.println(toString(listNodes[0]));
System.out.println(toString(listNodes[1]));
ListNode firstCommonNode = findFirstCommonNodeByMap(listNodes[0], listNodes[1]);
ListNode firstCommonNode2 = findFirstCommonNodeBySet(null, listNodes[1]);
ListNode stackNode = findFirstCommonNodeByStack(listNodes[0], listNodes[1]);
ListNode combine = findFirstCommonNodeByCombine(listNodes[0], listNodes[1]);
ListNode sub = findFirstCommonNodeBySub(listNodes[0], listNodes[1]);
// System.out.println(toString(firstCommonNode));
// System.out.println(toString(firstCommonNode2));
// System.out.println(toString(stackNode));
// System.out.println(toString(combine));
System.out.println(toString(sub));
}
/**
* 方法1:通过Hash辅助查找
*
* @param pHead1
* @param pHead2
* @return
*/
public static ListNode findFirstCommonNodeByMap(ListNode pHead1, ListNode pHead2) {
//warning 忘记判空了
if (pHead1 == null || pHead2 == null) {
return null;
}
Map<ListNode, Integer> map = new HashMap<>();
ListNode p1 = pHead1;
ListNode p2 = pHead2;
//将第一个链表的节点依次放入map
while (p1 != null) {
map.put(p1, null);
p1 = p1.next;
}
while (p2 != null) {
if (map.containsKey(p2)) {
return p2;
}
p2 = p2.next;
}
return null;
}
/**
* 方法2:通过集合来辅助查找
*
* @param headA
* @param headB
* @return
*/
public static ListNode findFirstCommonNodeBySet(ListNode headA, ListNode headB) {
ListNode h1 = headA;
ListNode h2 = headB;
List<ListNode> list = new ArrayList<>();
while (h1 != null) {
list.add(h1);
h1 = h1.next;
}
while (h2 != null) {
if (list.contains(h2)) {
return h2;
}
h2 = h2.next;
}
return null;
}
/**
* 方法3:通过栈
*/
public static ListNode findFirstCommonNodeByStack(ListNode headA, ListNode headB) {
Stack<ListNode> h1 = new Stack<>();
Stack<ListNode> h2 = new Stack<>();
ListNode ha = headA;//因为Java是值传递,所以不会改变外部头节点的取值,这2行代码去掉也无妨
ListNode hb = headB;
while (ha != null) {
h1.push(ha);
ha = ha.next;
}
while (hb != null) {
h2.push(hb);
hb = hb.next;
}
// while (!h1.empty()) {
// ListNode temp1 = h1.pop();
// ListNode temp2 = h2.pop();
// if (temp1 == temp2) {
// ListNode temp3 = h1.pop();
// ListNode temp4 = h2.pop();
// if (temp3 != temp4) {
// return temp1;
// }
// }
// }
//优化
//1、用一个变量保存上一个节点
//2、增加判断2个栈大小的逻辑,过滤空栈
// ListNode pre = null;
// while(h1.size() > 0 && h2.size() > 0){
// if(h1.peek() == h2.peek()){
pre = h1.peek();
h1.pop();
h2.pop();
// //优化
// pre = h1.pop();
// h2.pop();
// if(h1.peek() != h2.peek()){
// return pre;
// }
// }else {
// return null;
// }
// }
// return null;
//还能优化
ListNode pre = null;
while (h1.size() > 0 && h2.size() > 0) {
if (h1.peek() == h2.peek()) {
//优化
pre = h1.pop();
h2.pop();
} else {
break;
}
}
return pre;
}
/**
* 方法4:通过序列拼接(空间复杂度为O(1))
*/
public static ListNode findFirstCommonNodeByCombine(ListNode pHead1, ListNode pHead2) {
//双指针法
//算法思想:如果有A、B,2个链表,拼接成AB,BA,如果AB有公共节点,那肯定是唯一公共节点(由单链表定义可知)
//所以一直遍历到相同的公共节点,就是要找的第一个公共节点
//判空
if (pHead1 == null && pHead2 == null) {
return null;
}
ListNode p1 = pHead1;
ListNode p2 = pHead2;
while (p1 != p2) {
//这里的p1 != p2不能替换成true,否则遇到没有公共节点的情况会死循环
p1 = p1.next;
p2 = p2.next;
if (p1 != p2) {
if (p1 == null) {
p1 = pHead2;
}
if (p2 == null) {
p2 = pHead1;
}
}
}
return p1;
}
/**
* 方法5:通过差值来实现(空间复杂度为O(1))
*
* @param pHead1
* @param pHead2
* @return
*/
public static ListNode findFirstCommonNodeBySub(ListNode pHead1, ListNode pHead2) {
//虽然代码看起来比之前的几种方法多了不少,但思路也比较简单,很巧妙
//算法思想:首先遍历一次,获取2个链表的长度,然后再次遍历,第二次遍历,先让长的链表先走长度差值的距离
//然后2个链表再一起往下走,最后相等的第一个节点,就是要找的节点
//有一种对齐的思想在里面
//判空
if (pHead1 == null && pHead2 == null) {
return null;
}
ListNode p1 = pHead1;
ListNode p2 = pHead2;
int lengthP1 = 0;
int lengthP2 = 0;
//分别获取2个链表的长度
while (p1 != null) {
p1 = p1.next;
lengthP1++;
}
while (p2 != null) {
p2 = p2.next;
lengthP2++;
}
//计算差值,确保结果为绝对值
int sub = lengthP1 > lengthP2 ? lengthP1 - lengthP2 : lengthP2 - lengthP1;
p1 = pHead1;
p2 = pHead2;
//再次进行遍历
if (lengthP1 > lengthP2) {
while (sub > 0) {
p1 = p1.next;
sub--;
}
} else {
while (sub > 0) {
p2 = p2.next;
sub--;
}
}
while (p1 != p2) {
p1 = p1.next;
p2 = p2.next;
}
return p1;
}
/**
* 简单构造两个链表
*
* @return
*/
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;
}
static class ListNode {
public int val;
public ListNode next;
ListNode(int x) {
val = x;
next = null;
}
}
public static String toString(ListNode head) {
ListNode current = head;
StringBuilder sb = new StringBuilder();
while (current != null) {
sb.append(current.val).append("\t");
current = current.next;
}
return sb.toString();
}
}