题目描述
输入两个链表,找出它们的第一个公共结点。(注意因为传入数据是链表,所以错误测试数据的提示是用其他方式显示的,保证传入数据是正确的)


本题最容易想到的肯定是最暴力的方法:拿某一个链表的元素,去和另一个链表的所有节点匹配,然后再换一个,再拿去全部匹配一次。这样的时间复杂度肯定不会让你拿到offer。所以要优化啊。
  如果经常用Java刷题的话,本题肯定也会想到使用Hash表来判断是否重复。虽然是个方法,但总使用工具类解决,在面试中不占便宜。
  本题要深入本质才行,一旦两个链表有公共结点,就意味着两个链表可能不止有一个公共节点。两个链表在6处开始公共,则意味着6后面的所有节点都是公共的了。注意{1,4,5}和{1,3,7}中的1并不是公共节点,因为这两个节点只是val这个属性相同,next属性是不同的
图片说明
解法很多,在不使用工具类时,先上个暴力的:

public class Solution {
    public ListNode FindFirstCommonNode(ListNode pHead1, ListNode pHead2) {
       while(pHead1 != null){
            ListNode node = pHead2;//用一个指针用来遍历第二个链表
            while(node != null){
                if(pHead1 == node){//相同就返回这个节点
                    return node;
                }
                node = node.next;//否则就换下一个节点(第二个链表)
            }
            pHead1 = pHead1.next;//没有相同的,比较下一个节点(第一个链表)
        }
      return null;
    }
}

思路1:

  使用栈的方法,也就是用两个辅助栈,分别装两个链表,装完后,栈顶元素肯定相同,这个时候就两个栈同时弹出栈顶元素,直到栈顶不再相同时,就说明刚刚弹出的就是第一次相同的节点(比如上图中,分别入两个栈后,两个栈顶都是7,同时弹,这时再弹6,就发现一个栈顶是5,一个是3,不相同了,就返回6)。

import java.util.Stack;
public class Solution {
    public ListNode FindFirstCommonNode(ListNode pHead1, ListNode pHead2) {
    if (pHead1 == null || pHead2 == null) {
            return null;
        }
        Stack<ListNode> stack1 = new Stack<>();
        Stack<ListNode> stack2 = new Stack<>();

        while (pHead1 != null) {
            stack1.push(pHead1);
            pHead1 = pHead1.next;
        } 
        while (pHead2 != null) {
            stack2.push(pHead2);
            pHead2 = pHead2.next;
        }
 //Stack中装的是节点,因此不能比值,应该比节点。之前也说过,只是某个属性相同的节点不能算公共的
//==是比较对象的地址
        ListNode Node = null;
        while(!stack1.isEmpty() && !stack2.isEmpty() && stack1.peek()==stack2.peek()){
            stack2.pop();
            Node = stack1.pop();;
        }
        return Node;
    }
}

思路2:

  利用两个指针:一个指针顺序遍历list1和list2,另一个指针顺序遍历list2和list1,(这样两指针能够保证最终同时走到尾结点),两个指针找到的第一个相同结点就是第一个公共结点。过程,如上图的两个链表,遍历过程是(1,2,3,6,7,4,5,6,7)另一个指针的遍历顺序是(4,5,6,7,1,2,3,6,7),可以看到两个链表的遍历长度一样,且步调也一致时。最终会在6碰面。因此,代码中的过程就是,list1遍历完后,开始遍历list2,另一个则是list2遍历完后,开始遍历list1。
  相比于其他方法,这个方法的细节比较多。我是觉得如果不熟悉,在面试的时候不容易写出来。看似精短,实则不容易看懂。
  写法过程见注释

public class Solution {
    public ListNode FindFirstCommonNode(ListNode pHead1, ListNode pHead2) {
        if(pHead1 == null || pHead2 == null)
            return null;
        ListNode p1 = pHead1;
        ListNode p2 = pHead2;
        while(p1!=p2){//不相同肯定要分别移动到下一个
            p1 = p1.next;
            p2 = p2.next;
            if(p1 != p2){//如果相同,跳过。
//只有节点不同,且移动到null时,才需要换接另一个链表。但是两者都为null时,需要直接返回
//所以才必须加一个p1和p2相等的判断
                if(p1 == null)
                    p1 = pHead2;
                if(p2 == null)
                    p2 = pHead1;
            }
        }
        return p1;
    }
}

思路3:

快慢指针的方法。先遍历两次链表,然后统计节点数,让快指针先走完两个链表的差距,然后两个链表在同一起跑线上移动并比较(剩下的节点数一样)。这个方法和思路2有点相似,都是调整到数量相同且步调一致的时候比较。

public class Solution {
    public ListNode FindFirstCommonNode(ListNode pHead1, ListNode pHead2) {
        ListNode curNode1 = pHead1;
        ListNode curNode2 = pHead2;
        // 算 pHead1 长度
        int len1 = 0;
        while (curNode1 != null) {
          len1++;
          curNode1 = curNode1.next;
        }
        // 算 pHead2 长度
        int len2 = 0;
        while (curNode2 != null) {
          len2++;
          curNode2 = curNode2.next;
        }
        // 快慢指针开始缩短两个链表的差距了
        curNode1 = pHead1;
        curNode2 = pHead2;
        if (len1 >= len2) {//第一个链表比较长时
          for (int i = 0; i < len1 - len2; i++) {
            curNode1 = curNode1.next;
          }
        } else {//第2个链表比较长时
          for (int i = 0; i < len2 - len1; i++) {
            curNode2 = curNode2.next;
          }
        }
        while (curNode1 != curNode2) {//同一起跑线了
          curNode1 = curNode1.next;
          curNode2 = curNode2.next;
        }
        return curNode1;
    }
}