链表
面试时链表解题的方法论
- 对于笔试,不用太在乎空间复杂度,一切为了时间复杂度
- 对于面试,时间复杂度依然放在第一位,但是一定要找到空间最省的方法
快慢指针
- 输入链表头节点,奇数长度返回中点,偶数长度返回上中点
- 输入链表头节点,奇数长度返回中点,偶数长度返回下中点
- 输入链表头节点,奇数长度返回中点前一个,偶数长度返回上中点前一个
- 输入链表头节点,奇数长度返回中点前一个,偶数长度返回下中点前一个
package LinkedList;
import java.util.ArrayList;
public class LinkedListMid {
public static class Node{
public int value;
public Node next;
public Node(int v){
this.value=v;
}
}
public static Node midOrUpMidNode(Node head){
if (head==null||head.next==null||head.next.next==null) {
return head;
}
// 链表有3个点或以上
Node slow=head.next;
Node fast=head.next.next;
while(fast.next!=null&&fast.next.next!=null){
slow=slow.next;
fast=fast.next.next;
}
return slow;
}
public static Node midOrDownMidNode(Node head){
if(head==null||head.next==null){
return head;
}
Node slow=head.next;
Node fast=head.next;
while(fast.next!=null&&fast.next.next!=null){
slow=slow.next;
fast=fast.next.next;
}
return slow;
}
public static Node midOrUpMidPreNode(Node head) {
if (head == null || head.next == null || head.next.next == null) {
return null;
}
Node slow=head;
Node fast=head.next.next;
while(fast.next!=null&&fast.next.next!=null){
slow=slow.next;
fast=fast.next.next;
}
return slow;
}
public static Node midOrDownMidPreNode(Node head) {
if (head == null || head.next == null ) {
return null;
}
Node slow=head;
Node fast=head.next;
while(fast.next!=null&&fast.next.next!=null){
slow=slow.next;
fast=fast.next.next;
}
return slow;
}
//笔试用下面,面试用上面(装逼)
public static Node right1(Node head) {
if(head==null){
return null;
}
Node cur=head;
ArrayList<Node> arr=new ArrayList<>();
while(cur!=null){
arr.add(cur);
cur=cur.next;
}
return(arr.get((arr.size()-1)/2));
}
public static Node right2(Node head) {
if(head==null){
return null;
}
Node cur=head;
ArrayList<Node> arr=new ArrayList<>();
while(cur!=null){
arr.add(cur);
cur=cur.next;
}
return(arr.get(arr.size()/2));
}
public static Node right3(Node head) {
if(head==null||head.next==null||head.next.next==null){
return null;
}
Node cur=head;
ArrayList<Node> arr=new ArrayList<>();
while(cur!=null){
arr.add(cur);
cur=cur.next;
}
return(arr.get((arr.size()-3)/2));
}
public static Node right4(Node head) {
if(head==null||head.next==null){
return null;
}
Node cur=head;
ArrayList<Node> arr=new ArrayList<>();
while(cur!=null){
arr.add(cur);
cur=cur.next;
}
return(arr.get((arr.size()-2)/2));
}
public static void main(String[] args) {
Node test = null;
test = new Node(0);
test.next = new Node(1);
test.next.next = new Node(2);
test.next.next.next = new Node(3);
test.next.next.next.next = new Node(4);
test.next.next.next.next.next = new Node(5);
test.next.next.next.next.next.next = new Node(6);
test.next.next.next.next.next.next.next = new Node(7);
//test.next.next.next.next.next.next.next.next = new Node(8);
Node ans1 = null;
Node ans2 = null;
ans1 = midOrUpMidNode(test);
ans2 = right1(test);
System.out.println("midOrUpMidNode:");
System.out.println(ans1 != null ? ans1.value : "无");
System.out.println("right1:");
System.out.println(ans2 != null ? ans2.value : "无");
ans1 = midOrDownMidNode(test);
ans2 = right2(test);
System.out.println("midOrDownMidNode");
System.out.println(ans1 != null ? ans1.value : "无");
System.out.println("right2");
System.out.println(ans2 != null ? ans2.value : "无");
ans1 = midOrUpMidPreNode(test);
ans2 = right3(test);
System.out.println("midOrUpMidPreNode");
System.out.println("right3");
System.out.println(ans1 != null ? ans1.value : "无");
System.out.println(ans2 != null ? ans2.value : "无");
ans1 = midOrDownMidPreNode(test);
ans2 = right4(test);
System.out.println("midOrDownMidPreNode");
System.out.println(ans1 != null ? ans1.value : "无");
System.out.println("right4");
System.out.println(ans2 != null ? ans2.value : "无");
}
}
/*midOrUpMidNode:
3
right1:
3
midOrDownMidNode
4
right2
4
midOrUpMidPreNode
right3
2
2
midOrDownMidPreNode
3
right4
3
总结:
从上面的结果分分析,笔试用下面四种方法,面试也可以用上面的
必须有:
if (条件 ) {
return 值;
}
Node slow=;//需要判断
Node fast=;//需要判断
while(fast.next!=null&&fast.next.next!=null){
slow=slow.next;
fast=fast.next.next;
}
return slow;
public static Node right(Node head) {
if(head==null||head.next==null){
return null;
}
Node cur=head;
ArrayList<Node> arr=new ArrayList<>();
while(cur!=null){
arr.add(cur);
cur=cur.next;
}
return(arr.get(根据实际情况计算));
}
链表是否回文
给定一个单链表的头节点head,请判断该链表是否为回文结构。
1)栈方法特别简单(笔试用)
2)改原链表的方法就需要注意边界了(面试用)
思想:
方法1:用栈来把链表整个都装进去,然后再从链表头部遍历,和栈顶元素匹配。只要有一个不匹配,false。需要另外的内存O(N)
方法2:利用快慢指针走到中间位置,奇数长度走到中点,偶数长度走到上中点,然后只遍历后半部分存入栈,和之前的做对比需要另外的内存O(N/2)
方法3:不开辟额外的空间,通过快慢指针遍历到中间位置,把中间位置之后的指向都反转,然后最左边左指针,最右边右指针,通过不断遍历,判断。
package LinkedList;
import java.util.Stack;
public class IsPalindromeList {
public static class Node{
public int value;
public Node next;
public Node(int v){
this.value=v;
}
}
public static boolean isPalindrome1(Node head){
Stack<Node> stack=new Stack<>();
Node cur=head;
while(cur!=null){
stack.add(cur);
cur=cur.next;
}
while(head!=null){
if(head.value!=stack.pop().value){
return false;
}
head=head.next;
}
return true;
}
public static boolean isPalindrome2(Node head){
if (head==null||head.next==null) {
return true;
}
Node slow=head.next;
Node fast=head;
while(fast.next!=null&&fast.next.next!=null){
slow=slow.next;
fast=fast.next.next;
}
Stack<Node> stack=new Stack<>();
Node cur=slow;
while(cur!=null){
stack.add(cur);
cur=cur.next;
}
while(!stack.isEmpty()){
if(head.value!=stack.pop().value){
return false;
}
head=head.next;
}
return true;
}
public static boolean isPalindrome3(Node head){
if (head==null||head.next==null) {
return true;
}
Node slow=head;
Node fast=head;
while(fast.next!=null&&fast.next.next!=null){
slow=slow.next; //slow->mid
fast=fast.next.next; //fast->end
}
fast=slow.next; //fast->右边第一个节点
slow.next=null; //mid.next->null
Node n1=null;
while(fast!=null){//右边部分反转
n1=fast.next;//n1->保存下一个节点
fast.next=slow;//下一个右节点反转
slow=fast;//slow移动
fast=n1;//fast移动
}
n1=slow;//n1 save最后一个节点,通过这个点逆序回去
fast=head;//fast 左边第一个节点
boolean res=true;
while(slow!=null&&fast!=null){
if(slow.value!=fast.value){
res=false;
break;
}
fast=fast.next;
slow=slow.next;
}
//后面的操作是为了把列表复原回去
slow=n1.next;
n1.next=null;
while(slow!=null){//recover list
fast=slow.next;
slow.next=n1;
n1=slow;
slow=fast;
}
return res;
}
public static void printLinkedList(Node node) {
System.out.print("Linked List: ");
while (node != null) {
System.out.print(node.value + " ");
node = node.next;
}
System.out.println();
}
public static void main(String[] args) {
Node head = null;
printLinkedList(head);
System.out.print(isPalindrome1(head) + " | ");
System.out.print(isPalindrome2(head) + " | ");
System.out.println(isPalindrome3(head) + " | ");
printLinkedList(head);
System.out.println("=========================");
head = new Node(1);
printLinkedList(head);
System.out.print(isPalindrome1(head) + " | ");
System.out.print(isPalindrome2(head) + " | ");
System.out.println(isPalindrome3(head) + " | ");
printLinkedList(head);
System.out.println("=========================");
head = new Node(1);
head.next = new Node(2);
printLinkedList(head);
System.out.print(isPalindrome1(head) + " | ");
System.out.print(isPalindrome2(head) + " | ");
System.out.println(isPalindrome3(head) + " | ");
printLinkedList(head);
System.out.println("=========================");
head = new Node(1);
head.next = new Node(1);
printLinkedList(head);
System.out.print(isPalindrome1(head) + " | ");
System.out.print(isPalindrome2(head) + " | ");
System.out.println(isPalindrome3(head) + " | ");
printLinkedList(head);
System.out.println("=========================");
head = new Node(1);
head.next = new Node(2);
head.next.next = new Node(3);
printLinkedList(head);
System.out.print(isPalindrome1(head) + " | ");
System.out.print(isPalindrome2(head) + " | ");
System.out.println(isPalindrome3(head) + " | ");
printLinkedList(head);
System.out.println("=========================");
head = new Node(1);
head.next = new Node(2);
head.next.next = new Node(1);
printLinkedList(head);
System.out.print(isPalindrome1(head) + " | ");
System.out.print(isPalindrome2(head) + " | ");
System.out.println(isPalindrome3(head) + " | ");
printLinkedList(head);
System.out.println("=========================");
head = new Node(1);
head.next = new Node(2);
head.next.next = new Node(3);
head.next.next.next = new Node(1);
printLinkedList(head);
System.out.print(isPalindrome1(head) + " | ");
System.out.print(isPalindrome2(head) + " | ");
System.out.println(isPalindrome3(head) + " | ");
printLinkedList(head);
System.out.println("=========================");
head = new Node(1);
head.next = new Node(2);
head.next.next = new Node(2);
head.next.next.next = new Node(1);
printLinkedList(head);
System.out.print(isPalindrome1(head) + " | ");
System.out.print(isPalindrome2(head) + " | ");
System.out.println(isPalindrome3(head) + " | ");
printLinkedList(head);
System.out.println("=========================");
head = new Node(1);
head.next = new Node(2);
head.next.next = new Node(3);
head.next.next.next = new Node(2);
head.next.next.next.next = new Node(1);
printLinkedList(head);
System.out.print(isPalindrome1(head) + " | ");
System.out.print(isPalindrome2(head) + " | ");
System.out.println(isPalindrome3(head) + " | ");
printLinkedList(head);
System.out.println("=========================");
}
}
/*
Linked List:
true | true | true |
Linked List:
=========================
Linked List: 1
true | true | true |
Linked List: 1
=========================
Linked List: 1 2
false | false | false |
Linked List: 1 2
=========================
Linked List: 1 1
true | true | true |
Linked List: 1 1
=========================
Linked List: 1 2 3
false | false | false |
Linked List: 1 2 3
=========================
Linked List: 1 2 1
true | true | true |
Linked List: 1 2 1
=========================
Linked List: 1 2 3 1
false | false | false |
Linked List: 1 2 3 1
=========================
Linked List: 1 2 2 1
true | true | true |
Linked List: 1 2 2 1
=========================
Linked List: 1 2 3 2 1
true | true | true |
Linked List: 1 2 3 2 1
=========================
链表值排序
将单向链表按某值划分成左边小、中间相等、右边大的形式
1)把链表放入数组里,在数组上做partition(笔试用)
2)分成小、中、大三部分,再把各个部分之间串起来(面试用)
package LinkedList;
public class SmallerEqualBigger {
public static class Node {
public int value;
public Node next;
public Node(int data) {
this.value = data;
}
}
public static Node listPartition1(Node head, int pivot) {
if (head==null) {
return head;
}
Node cur=head;
int i=0;
while(cur!=null){
i++;
cur=cur.next;
}
Node[] nodeArr = new Node[i];
i = 0;
cur = head;
for(i=0;i<nodeArr.length;i++){
nodeArr[i]=cur;
cur=cur.next;
}
arrPartition(nodeArr, pivot);
for(i=1;i!=nodeArr.length;i++){
nodeArr[i-1].next=nodeArr[i];
}
nodeArr[i-1].next=null;
return nodeArr[0];
}
public static void arrPartition(Node[] nodeArr, int pivot) {
int samll=-1;
int big=nodeArr.length;
int index=0;
while(index!=big){
if (nodeArr[index].value<pivot) {
swap(nodeArr, ++samll, index++);
}else if (nodeArr[index].value==pivot) {
index++;
}else {
swap(nodeArr, --big, index);
}
}
}
public static void swap(Node[] nodeArr, int a, int b) {
Node tmp = nodeArr[a];
nodeArr[a] = nodeArr[b];
nodeArr[b] = tmp;
}
//方法2
public static Node listPartition2(Node head, int pivot){
Node sH=null;//small head
Node sT=null;//small tail
Node eH=null;//equal head
Node eT=null;//equal tail
Node bH=null;//big head
Node bT=null;//big tail
Node next=null;// save next node
// every node distributed to three lists
while(head!=null){
next=head.next;
head.next=null;
if (head.value<pivot) {
if (sH==null) {
sH=head;//当前节点既做头,又做尾
sT=head;
}else {
sT.next=head;//老尾巴的next指针指向当前节点
sT=head;//当前节点变成新尾巴
}
}else if (head.value==pivot) {
if (eH==null) {
eH=head;//当前节点既做头,又做尾
eT=head;
}else {
eT.next=head;//老尾巴的next指针指向当前节点
eT=head;//当前节点变成新尾巴
}
}else {
if (bH==null) {
bH=head;//当前节点既做头,又做尾
bT=head;
}else {
bT.next=head;//老尾巴的next指针指向当前节点
bT=head;//当前节点变成新尾巴
}
}
head = next;
}
// 小于区域的尾巴,连等于区域的头,等于区域的尾巴连大于区域的头
if (sT!=null) {//如果有小于区域
sT.next=eH;
eT=eT==null? sT:eT;//下一步谁去连大于区域的头,谁就变成eT
}
//上面的if,不管中没中,都是eT去连大于区域。
// 下一步,一定是需要用eT 去接 大于区域的头
// 有等于区域,eT -> 等于区域的尾结点
// 无等于区域,eT -> 小于区域的尾结点
if (eT!=null) {
eT.next=bH;// 如果小于区域和等于区域,不是都没有
}
return sH!=null? sH:(eH!=null ? eH:bH);
}
public static void printLinkedList(Node node) {
System.out.print("Linked List: ");
while (node != null) {
System.out.print(node.value + " ");
node = node.next;
}
System.out.println();
}
public static void main(String[] args) {
Node head1 = new Node(7);
head1.next = new Node(9);
head1.next.next = new Node(1);
head1.next.next.next = new Node(8);
head1.next.next.next.next = new Node(5);
head1.next.next.next.next.next = new Node(2);
head1.next.next.next.next.next.next = new Node(5);
printLinkedList(head1);
// head1 = listPartition1(head1, 4);
head1 = listPartition2(head1, 5);
printLinkedList(head1);
}
}
/*Linked List: 7 9 1 8 5 2 5
Linked List: 1 2 5 5 7 9 8
链表复制(函数,rand)
一种特殊的单链表节点类描述如下
class Node{
int value;
Node next;
Node rand;
Node(int val){
value=val;
}
rand指针是单链表节点结构中新增的指针,rand可能指向链表中的任意一个节点,也可能指向nul。
给定一个由Node节点类型组成的无环单链表的头节点head,请实现一个函数完成这个链表的复制,并返回复制的新链表的头节点。
【要求】
时间复杂度O(N),额外空间复杂度O(1)
思想:用一个map来存老节点和新节点。
package LinkedList;
import java.util.HashMap;
public class CopyListWithRandom {
public static class Node {
public int value;
public Node next;
public Node rand;
public Node(int data) {
this.value = data;
}
}
public static Node copyListWithRand1(Node head) {
// key 老节点
// value 新节点
HashMap<Node, Node> map=new HashMap<>();
Node cur=head;
while(cur!=null){
map.put(cur, new Node(cur.value));
cur=cur.next;
}
cur=head;
while(cur!=null){
// cur 老
// map.get(cur) 新
// 新.next -> cur.next克隆节点找到
map.get(cur).next=map.get(cur.next);
map.get(cur).rand=map.get(cur.rand);
cur=cur.next;
}
return map.get(head);
}
public static Node copyListWithRand2(Node head) {
if (head==null) {
return head;
}
Node cur=head;
Node next=null;
// copy node and link to every node
// 1 -> 2
// 1 -> 1' -> 2
while(cur!=null){
//cur 老节点 next下一个节点
next=cur.next;
cur.next=new Node(cur.value);
cur.next.next=next;
cur=next;
}
cur=head;
Node curCopy=null;
// set copy node rand
// 1 -> 1' -> 2 -> 2'
while(cur!=null){
// cur 老
// cur.next 新 copy
next=cur.next.next;
curCopy=cur.next;
curCopy.rand=cur.rand!=null?cur.rand.next:null;
cur=next;
}
// head head.next
Node res = head.next;
cur = head;
// split
while(cur!=null){
next=cur.next.next;
curCopy=cur.next;
cur.next=next;
curCopy.next=next!=null?next.next:null;
cur=next;
}
return res;
}
public static void printRandLinkedList(Node head) {
Node cur = head;
System.out.print("order: ");
while (cur != null) {
System.out.print(cur.value + " ");
cur = cur.next;
}
System.out.println();
cur = head;
System.out.print("rand: ");
while (cur != null) {
System.out.print(cur.rand == null ? "- " : cur.rand.value + " ");
cur = cur.next;
}
System.out.println();
}
public static void main(String[] args) {
Node head = null;
Node res1 = null;
Node res2 = null;
printRandLinkedList(head);
res1 = copyListWithRand1(head);
printRandLinkedList(res1);
res2 = copyListWithRand2(head);
printRandLinkedList(res2);
printRandLinkedList(head);
System.out.println("=========================");
head = new Node(1);
head.next = new Node(2);
head.next.next = new Node(3);
head.next.next.next = new Node(4);
head.next.next.next.next = new Node(5);
head.next.next.next.next.next = new Node(6);
head.rand = head.next.next.next.next.next; // 1 -> 6
head.next.rand = head.next.next.next.next.next; // 2 -> 6
head.next.next.rand = head.next.next.next.next; // 3 -> 5
head.next.next.next.rand = head.next.next; // 4 -> 3
head.next.next.next.next.rand = null; // 5 -> null
head.next.next.next.next.next.rand = head.next.next.next; // 6 -> 4
System.out.println("原始链表:");
printRandLinkedList(head);
System.out.println("=========================");
res1 = copyListWithRand1(head);
System.out.println("方法一的拷贝链表:");
printRandLinkedList(res1);
System.out.println("=========================");
res2 = copyListWithRand2(head);
System.out.println("方法二的拷贝链表:");
printRandLinkedList(res2);
System.out.println("=========================");
System.out.println("经历方法二拷贝之后的原始链表:");
printRandLinkedList(head);
System.out.println("=========================");
}
}
/*
order:
rand:
order:
rand:
order:
rand:
order:
rand:
=========================
原始链表:
order: 1 2 3 4 5 6
rand: 6 6 5 3 - 4
=========================
方法一的拷贝链表:
order: 1 2 3 4 5 6
rand: 6 6 5 3 - 4
=========================
方法二的拷贝链表:
order: 1 2 3 4 5 6
rand: 6 6 5 3 - 4
=========================
经历方法二拷贝之后的原始链表:
order: 1 2 3 4 5 6
rand: 6 6 5 3 - 4
=========================
链表环的综合题
给定两个可能有环也可能无环的单链表,头节点head1和head2。请实现一个函数,如果两个链表相交,请返回相交的第一个节点。如果不相交,返回null
【要求】
如果两个链表长度之和为N,时间复杂度请达到O(N),额外空间复杂度请达到O(1)。
找到链表第一个入环点,如果无环,返回null
如果两个链表都无环,返回第一个相交节点,如果不想交,返回null
两个有环链表相交,,要么不想交,要么相交一点,相交多个点,(都有换的相交必是公用一个环)
package LinkedList;
public class FindFirstIntersectNode {
public static class Node {
public int value;
public Node next;
public Node(int data) {
this.value = data;
}
}
public static Node getIntersectNode(Node head1,Node head2){
if(head1==null||head2==null){
return null;
}
Node loop1=getLoopNode(head1);
Node loop2=getLoopNode(head2);
if (loop1==null&&loop2==null) {
return noLoop(head1, head2);
}
if (loop1!=null&&loop2!=null) {
return bothLoop(head1, loop1, head2, loop2);
}
return null;
}
//找到链表第一个入环点,如果无环,返回null
public static Node getLoopNode(Node head){
if (head==null||head.next==null||head.next.next==null) {
return null;
}
//n1慢指针,n2快指针
Node n1=head.next;
Node n2=head.next.next;
while(n1!=n2){
if (n2.next==null||n2.next.next==null) {
return null;
}
n2=n2.next.next;
n1=n1.next;
}
n2=head;// n1 n2 相遇 n2 -> walk again from head
while(n1!=n2){
n1=n1.next;
n2=n2.next;
}
return n1;
}
// 如果两个链表都无环,返回第一个相交节点,如果不想交,返回null
public static Node noLoop(Node head1, Node head2) {
if (head1==null||head2==null) {
return null;
}
Node cur1=head1;
Node cur2=head2;
int n=0;
while(cur1.next!=null){
n++;
cur1=cur1.next;
}
while(cur2.next!=null){
n--;
cur2=cur2.next;
}
if (cur1!=cur2) {
return null;
}
// n : 链表1长度减去链表2长度的值
cur1 = n > 0 ? head1 : head2; // 谁长,谁的头变成cur1
cur2 = cur1 == head1 ? head2 : head1; // 谁短,谁的头变成cur2
Math.abs(n);
while(n!=0){
n--;
cur1=cur1.next;
}
while(cur1!=cur2){
cur1=cur1.next;
cur2=cur2.next;
}
return cur1;
}
// 两个有环链表,返回第一个相交节点,如果不想交返回null
public static Node bothLoop(Node head1,Node loop1,Node head2,Node loop2){
Node cur1=null;
Node cur2=null;
if (loop1==loop2) {
cur1=head1;
cur2=head2;
int n=0;
while(cur1!=loop1){
n++;
cur1=cur1.next;
}
while(cur2!=loop2){
n--;
cur2=cur2.next;
}
cur1=n>0?head1:head2;
cur2=cur1==head1?head2:head1;
n=Math.abs(n);
while(n!=0){
n--;
cur1=cur1.next;
}
while(cur1!=cur2){
cur1=cur1.next;
cur2=cur2.next;
}
return cur1;
}else{
cur1=loop1.next;
while(cur1!=loop1){
if(cur1==loop2){
return loop1;
}
cur1=cur1.next;
}
return null;
}
}
public static void main(String[] args) {
// 1->2->3->4->5->6->7->null
Node head1 = new Node(1);
head1.next = new Node(2);
head1.next.next = new Node(3);
head1.next.next.next = new Node(4);
head1.next.next.next.next = new Node(5);
head1.next.next.next.next.next = new Node(6);
head1.next.next.next.next.next.next = new Node(7);
// 0->9->8->6->7->null
Node head2 = new Node(0);
head2.next = new Node(9);
head2.next.next = new Node(8);
head2.next.next.next = head1.next.next.next.next.next; // 8->6
System.out.println(getIntersectNode(head1, head2).value);
// 1->2->3->4->5->6->7->4...
head1 = new Node(1);
head1.next = new Node(2);
head1.next.next = new Node(3);
head1.next.next.next = new Node(4);
head1.next.next.next.next = new Node(5);
head1.next.next.next.next.next = new Node(6);
head1.next.next.next.next.next.next = new Node(7);
head1.next.next.next.next.next.next = head1.next.next.next; // 7->4
// 0->9->8->2...
head2 = new Node(0);
head2.next = new Node(9);
head2.next.next = new Node(8);
head2.next.next.next = head1.next; // 8->2
System.out.println(getIntersectNode(head1, head2).value);
// 0->9->8->6->4->5->6..
head2 = new Node(0);
head2.next = new Node(9);
head2.next.next = new Node(8);
head2.next.next.next = head1.next.next.next.next.next; // 8->6
System.out.println(getIntersectNode(head1, head2).value);
}
}
/*6
2
6
上述的解答步骤可以拆分为三种题,
1:给链表找第一个入环节点,
2:如果两个链表都无环,返回第一个相交节点,如果不想交,返回null
3:两个有环链表相交,要么不想交,要么相交一点,相交多个点,(都有环的相交必是公用一个环)