题目描述:有一个单链表,其中可能有一个环,也就是某个节点的next指向的是链表中在它之前的节点,这样在链表的尾部形成一环。问题:
1、如何判断一个链表是不是这类链表
2、如果链表为存在环,如何找到环的入口点

一、判断链表是否存在环
设置两个指针(fast, slow),初始值都指向头,slow每次前进一步,fast每次前进二步,如果链表存在环,则fast必定先进入环,而slow后进入环,两个指针必定相遇。(当然,fast先行头到尾部为NULL,则为无环链表)。

boolean isExsitLoop() {
   2:     Node<T> slow = head;
   3:     Node<T> fast = head;
   4:  
while (fast != null && fast.next != null) {
   6:         slow = slow.next;
   7:         fast = fast.next.next;
   8:  
if (slow == fast)
return true;
  11:     }
return false;
  13: }

二、找到环的入口点
当fast若与slow相遇时,slow肯定没有走遍历完链表,而fast已经在环内循环了n圈(1<=n)。假设slow走了s步,则fast走了2s步(fast步数还等于s 加上在环上多转的n圈),设环长为r,则:
2s = s + nr
s= nr
设整个链表长L,入口环与相遇点距离为x,起点到环入口点的距离为a。
a + x = nr
a + x = (n – 1)r +r = (n-1)r + L - a
a = (n-1)r + (L – a – x)
(L – a – x)为相遇点到环入口点的距离,由此可知,从链表头到环入口点等于(n-1)循环内环+相遇点到环入口点。于是我们从链表头、与相遇点分别设一个指针,每次各走一步,两个指针必定相遇,且相遇第一点为环入口点

   1: Node<T> getLoopEntry() {
   2:     Node<T> slow = head;
   3:     Node<T> fast = head;
   4:  
while (fast != null && fast.next != null) {
   6:         slow = slow.next;
   7:         fast = fast.next.next;
   8:  
if (slow == fast)
break;
  11:     }
  12:  
if (fast == null || fast.next == null)
return null;
  15:  
// slow指向链表头节点
// fast指向slow和fast相遇的节点
  18:     slow = head;
  19:  
while (slow != fast) {
  21:         slow = slow.next;
  22:         fast = fast.next;
  23:     }
  24:  
return fast;
  26: }    

2、两个链表是否相交

题目描述:给出两个单向链表的头指针(如下图所示),

单链表是否有环和两个链表是否有公共节点问题

比如h1、h2,判断这两个链表是否相交。

      直接循环判断第一个链表的每个节点是否在第二个链表中。但,这种方法的时间复杂度为O(Length(h1) * Length(h2))。显然,我们得找到一种更为有效的方法,至少不能是O(N^2)的复杂度。

  1. 针对第一个链表直接构造hash表,然后查询hash表,判断第二个链表的每个结点是否在hash表出现,如果所有的第二个链表的结点都能在hash表中找到,即说明第二个链表与第一个链表有相同的结点。时间复杂度为为线性:O(Length(h1) + Length(h2)),同时为了存储第一个链表的所有节点,空间复杂度为O(Length(h1))。是否还有更好的方法呢,既能够以线性时间复杂度解决问题,又能减少存储空间?
  2. 进一步考虑“如果两个没有环的链表相交于某一节点,那么在这个节点之后的所有节点都是两个链表共有的”这个特点,我们可以知道,如果它们相交,则最后一个节点一定是共有的。而我们很容易能得到链表的最后一个节点,所以这成了我们简化解法的一个主要突破口。那么,我们只要判断俩个链表的尾指针是否相等。相等,则链表相交;否则,链表不相交。
    所以,先遍历第一个链表,记住最后一个节点。然后遍历第二个链表,到最后一个节点时和第一个链表的最后一个节点做比较,如果相同,则相交,否则,不相交。这样我们就得到了一个时间复杂度,它为O((Length(h1) + Length(h2)),而且只用了一个额外的指针来存储最后一个节点。这个方法时间复杂度为线性O(N),空间复杂度为O(1),显然比解法三更胜一筹。
  3. 上面的问题都是针对链表无环的,那么如果现在,链表是有环的呢?还能找到最后一个结点进行判断么?上面的方法还同样有效么?显然,这个问题的本质已经转化为判断链表是否有环。那么,如何来判断链表是否有环呢?

所以,事实上,这个判断两个链表是否相交的问题就转化成了:
1、先判断带不带环。
2、如果都不带环,就判断尾节点是否相等。
3、如果都带环,判断一链表上俩指针相遇的那个节点(问题1中的相遇节点),在不在另一条链表上。如果在,则相交,如果不在,则不相交。

这里为了简化问题,我们假设两个链表均不带环。

一、判断两链表链表是否有公共点

如果都不带环,就判断尾节点是否相等即可。如果都带环,判断一链表上俩指针相遇的那个节点,在不在另一条链表上。

boolean isCommonNode(LinkList<Integer> link, LinkList<Integer> link2) {
   2:     Node<Integer> last;
   3:     Node<Integer> last2;
// 两链表均无环
if (link.isExsitLoop() == false && link2.isExsitLoop() == false) {
// 获取最后一个节点
   7:         last = (Node<Integer>) link.getLastNode();
   8:         last2 = (Node<Integer>) link2.getLastNode();
+last2.value);
return last.value == last2.value;
  11:     }
//一个有环,一个无环
if(link.isExsitLoop()!=link2.isExsitLoop())
  14:     {
return false;
  16:     }    
//两个都有环,判断环里的节点是否能到达另一个链表环里的节点
else
  19:     {
// 相遇节点
  21:         Node<Integer> meetNode = link.getMeetNode();
  22:         Node<Integer> meetNode2 = link2.getMeetNode();
  23:         Node<Integer> p = meetNode.next;
  24:         
// 环中遍历
while(p!=meetNode)
  27:         {
if(p == meetNode2)
return true;
  30:             p = p.next;
  31:         }
return false;
  33:     }
  34: }

二、求两个链表相交的第一个节点

        如果两个尾结点是一样的,说明它们有重合;否则两个链表没有公共的结点。 在上面的思路中,顺序遍历两个链表到尾结点的时候,我们不能保证在两个链表上同时到达尾结点。 这是因为两个链表不一定长度一样。但如果假设一个链表比另一个长L个结点,我们先在长的链表上遍历L个结点,之后再同步遍历,这个时候我们就能保证同时到达最后一个结点了。
        由于两个链表从第一个公共结点开始到链表的尾结点,这一部分是重合的。因此,它们肯定也是同时到达第一公共结点的。于是在遍历中,第一个相同的结点就是第一个公共的结点。

static Node<Integer> getFirstCommonNode(LinkList<Integer> link, LinkList<Integer> link2)
   2: {
   3:     Node<Integer> l = link.getHeadNode();
   4:     Node<Integer> s = link2.getHeadNode();
int len1 = link.getLength();
int len2 = link2.getLength();        
int diff = len1 - len2;  
   8:     
if(len1<len2)
  10:     {
  11:         l = link2.getHeadNode();
  12:         s = link.getHeadNode();
  13:         diff = len2 - len1;
  14:     }
  15:     
// 长的先走diff长度  
int i = 0; i < diff; i++ )  
  18:         l = l.next;  
  19:    
while(l!=null&&s!=null&&l.value!=s.value)
  21:     {
  22:         l = l.next;
  23:         s = s.next;
  24:     }
  25:     
  26:     Node<Integer> comNode = null ;
if(l.value==s.value)
  28:         comNode = l;
return comNode;
  30: }

具体代码为:

class Node<T> {
   2:     T value;
   3:     Node<T> next = null;
   4:  
boolean equals(Node<T> node) {
if (value.equals(node.value)) {
return true;
   8:         }
return false;
  10:     }
  11:  
int hashCode() {
return value.hashCode();
  14:     }
  15:  
public String toString() {
if (value == null)
;
return value.toString();
  20:     }
  21: }
  22:  
class LinkList<T> {
private Node<T> head;
int len;
  26:  
public LinkList() {
  28:         initLinkList();
  29:     }
  30:  
void initLinkList() {
// 空链表
  33:         len = 0;
  34:  
  35:         head.next = null;
  36:     }
  37:  
// 链表插入操作
void insertAfterHead(Node<T> node) {
  40:         node.next = head.next;
  41:         head.next = node;
  42:         len++;
  43:     }
  44:  
void insertAtLast(Node<T> node) {
  46:         Node<T> p = getLastNode();
  47:  
// null
  49:         p.next = node;
  50:         len++;
  51:  
  52:     }
  53:  
// 产生环
void insertFormLoop(Node<T> node) {
  56:         Node<T> p = getLastNode();
  57:  
// 产生环
  59:         p.next = node;
  60:         len++;
  61:     }
  62:  
  63:     Node<T> getHeadNode() {
return head;
  65:     }
  66:  
  67:     Node<T> getLastNode() {
  68:         Node<T> p = head;
// 获取最后一个节点
while (p.next != null) {
  71:             p = p.next;
  72:         }
return p;
  74:     }
  75:  
int getLength() {
return len;
  78:     }
  79:  
// 遍历
void traversal() {
  82:         Node<T> node = head.next;
while (node != null) {
);
  85:             node = node.next;
  86:         }
  87:         System.out.println();
  88:     }
  89:  
/* http://www.cppblog.com/humanchao/archive/2008/04/17/47357.html
     设置两个指针(fast, slow),初始值都指向头,slow每次前进一步,fast每次前进二步,如果链表存在环,则fast必定先进入环,而slow后进入环,两个指针必定相遇。
     (当然,fast先行头到尾部为NULL,则为无环链表)*/
// 判断是否有环
boolean isExsitLoop() {
  95:         Node<T> slow = head;
  96:         Node<T> fast = head;
  97:  
while (fast != null && fast.next != null) {
  99:             slow = slow.next;
 100:             fast = fast.next.next;
 101:  
if (slow == fast)
return true;
 104:         }
return false;
 106:     }
 107:     
// 有环的情况下,获取相遇节点
 109:     Node<T> getMeetNode()
 110:     {
 111:         Node<T> slow = head;
 112:         Node<T> fast = head;
 113:  
while (fast != null && fast.next != null) {
 115:             slow = slow.next;
 116:             fast = fast.next.next;
 117:  
if (slow == fast)
return slow;
 120:         }
return null;
 122:     }
 123:  
// 找出环的入口
/*
     * 当fast若与slow相遇时,slow肯定没有走遍历完链表,而fast已经在环内循环了n圈(1<=n)。假设slow走了s步,则fast走了2s步(fast步数还等于s 加上在环上多转的n圈),设环长为r,则:
       2s = s + nr
       s= nr
       设整个链表长L,入口环与相遇点距离为x,起点到环入口点的距离为a。
       a + x = nr
       a + x = (n – 1)r +r = (n-1)r + L - a
       a = (n-1)r + (L – a – x)
       (L – a – x)为相遇点到环入口点的距离,由此可知,从链表头到环入口点等于(n-1)循环内环+相遇点到环入口点,于是我们从链表头、与相遇点分别设一个指针,每次各走一步,两个指针必定相遇,且相遇第一点为环入口点。
     * */
 135:     Node<T> getLoopEntry() {
 136:         Node<T> slow = head;
 137:         Node<T> fast = head;
 138:  
while (fast != null && fast.next != null) {
 140:             slow = slow.next;
 141:             fast = fast.next.next;
 142:  
if (slow == fast)
break;
 145:         }
 146:  
if (fast == null || fast.next == null)
return null;
 149:  
// slow指向链表头节点
// fast指向slow和fast相遇的节点
 152:         slow = head;
 153:  
while (slow != fast) {
 155:             slow = slow.next;
 156:             fast = fast.next;
 157:         }
 158:  
return fast;
 160:     }    
 161:  
 162: }
 163:  
class LinkBasic {
// java内部类的初始化
class StaticInnerTest {
void innerClass() {
);
 169:         }
 170:     }
 171:     
/* http://blog.csdn.net/v_JULY_v/article/details/6447013
       判断是否有公共点
       1、两个链表都无环的情况:判断尾节点是否相等 
       2、如果都带环,判断一链表上俩指针相遇的那个节点,在不在另一条链表上。
    */
boolean isCommonNode(LinkList<Integer> link, LinkList<Integer> link2) {
 178:         Node<Integer> last;
 179:         Node<Integer> last2;
// 两链表均无环
if (link.isExsitLoop() == false && link2.isExsitLoop() == false) {
// 获取最后一个节点
 183:             last = (Node<Integer>) link.getLastNode();
 184:             last2 = (Node<Integer>) link2.getLastNode();
+last2.value);
return last.value == last2.value;
 187:         }
//一个有环,一个无环
if(link.isExsitLoop()!=link2.isExsitLoop())
 190:         {
return false;
 192:         }    
//两个都有环,判断环里的节点是否能到达另一个链表环里的节点
else
 195:         {
// 相遇节点
 197:             Node<Integer> meetNode = link.getMeetNode();
 198:             Node<Integer> meetNode2 = link2.getMeetNode();
 199:             Node<Integer> p = meetNode.next;
 200:             
// 环中遍历
while(p!=meetNode)
 203:             {
if(p == meetNode2)
return true;
 206:                 p = p.next;
 207:             }
return false;
 209:         }
 210:     }
 211:     
// 求两个链表相交的第一个节点
/*
     * 思路:如果两个尾结点是一样的,说明它们有重合;否则两个链表没有公共的结点。
        在上面的思路中,顺序遍历两个链表到尾结点的时候,我们不能保证在两个链表上同时到达尾结点。   
    这是因为两个链表不一定长度一样。但如果假设一个链表比另一个长L个结点,我们先在长的链表上遍历L个结点,
    之后再同步遍历,这个时候我们就能保证同时到达最后一个结点了。
        由于两个链表从第一个公共结点开始到链表的尾结点,这一部分是重合的。因此,它们肯定也是同时到达第一公共结点的。
    于是在遍历中,第一个相同的结点就是第一个公共的结点。
     * */
 221:     
static Node<Integer> getFirstCommonNode(LinkList<Integer> link, LinkList<Integer> link2)
 223:     {
 224:         Node<Integer> l = link.getHeadNode();
 225:         Node<Integer> s = link2.getHeadNode();
int len1 = link.getLength();
int len2 = link2.getLength();        
int diff = len1 - len2;  
 229:         
if(len1<len2)
 231:         {
 232:             l = link2.getHeadNode();
 233:             s = link.getHeadNode();
 234:             diff = len2 - len1;
 235:         }
 236:         
// 长的先走diff长度  
int i = 0; i < diff; i++ )  
 239:             l = l.next;  
 240:        
while(l!=null&&s!=null&&l.value!=s.value)
 242:         {
 243:             l = l.next;
 244:             s = s.next;
 245:         }
 246:         
 247:         Node<Integer> comNode = null ;
if(l.value==s.value)
 249:             comNode = l;
return comNode;
 251:     }
 252:  
/**
     * @param args
     */
void main(String[] args) {
// TODO Auto-generated method stub
new StaticInnerTest();
 259:         test.innerClass();
 260:  
/************************
         * 形成初始单链表
         * **********************/
new LinkList<Integer>();
new LinkList<Integer>();
int[] arr = { 1, 2, 3, 4, 5, 6 };
int[] arr2 = { 7, 1, 2, 3, 6, 8, 9, 10 };
int i = 0; i < arr.length; i++) {
new Node<Integer>();
 270:             node.value = arr[i];
 271:             link.insertAfterHead(node);
 272:         }
 273:  
int i = 0; i < arr2.length; i++) {
new Node<Integer>();
 276:             node.value = arr2[i];
 277:             link2.insertAfterHead(node);
 278:         }
 279:  
/************************
         * 在链表尾部插入新的结点
         * **********************/
new Node<Integer>();
 284:         node.value = 7;
 285:         link.insertAtLast(node);
 286:  
 + link.getLength());
 + link.getHeadNode().value);
);
 290:         link.traversal();
 291:  
 + link2.getLength());
);
 294:         link2.traversal();
 295:  
/************************
         * 判断两个链表是否有公共节点
         * **********************/
+isCommonNode(link,link2));
+getFirstCommonNode(link,link2).value);
 301:  
/************************
         * 在链表尾部插入结点,形成环
         * 
         * 即在链表的最后一个元素p后插入节点node,使node的next指针域指向p,形成环结构
         * **********************/
new Node<Integer>();
 308:         node2.value = 19;
 309:         link.insertFormLoop(node2);
if (link.isExsitLoop()) {
);
 + link.getLoopEntry().value);
 313:         }
 314:  
 315:     }
 316:  
 317: }

参考资料:

1、《编程之美》

2、程序员编程艺术:第九章、闲话链表追赶问题

3、判断单链表是否存在环,判断两个链表是否相交问题详解

4、找含单链表的环入口点

5、程序员面试题精选100题

相关文章: