JAVA 判断两个单链表是否相交并求交点

在上一篇文档中,通过java实现了单链表反转的问题,之后发现一个更有意思的问题就是如何判断两个链表是否相交?如果相交,则需要得到交点。
对于这个问题,需要分别考虑链表上是否存在环的情况。

//链表节点
public class DataNode {

    private int data;
    private DataNode next;

    public int getData() {
        return data;
    }

    public void setData(int data) {
        this.data = data;
    }

    public DataNode getNext() {
        return next;
    }

    public void setNext(DataNode next) {
        this.next = next;
    }

    public DataNode(int data) {
        this.data = data;
    }
    
}

1.两个链表都不存在环

对于这种情况,如果两个链表相交,又都不存在环,那么不难想象这两个链表共同构成了一个Y型。因此,只要分别遍历这两个链表,找到尾端节点,判断尾端节点是否相同即可确认是否相交。
如果要求这种情况的交点,由于相交部分全部都相同,因此,只需要先得到两个链表的差,用两个指针分别指向这两个链表P1,P2假定P1与P2相差为N,那么将P1移动N个节点后,P1与P2同时出发,第一个相等的节点即为交点。

/**
     * 无环情况下,判断两个链表是否相交,只需要遍历链表,判断尾节点是否相等即可。
     * @param h1
     * @param h2
     * @return
     */
    public static boolean isJoinNoLoop(DataNode h1,DataNode h2) {
        DataNode p1 = h1;
        DataNode p2 = h2;
        while(null != p1.getNext())
            p1 = p1.getNext();
        while(null != p2.getNext())
            p2 = p2.getNext();
        return p1 == p2;
    }
    
    /**
     * 无环情况下找到第一个相交点
     * 方法: 算出两个链表的长度差为x,长链表先移动x步,之后两链表同时移动,直到相遇的第一个交点。
     * @param h1
     * @param h2
     * @return
     */
    public static DataNode getFirstJoinNode(DataNode h1,DataNode h2) {
        int length1 = 0;
        int length2 = 0;
        while(null != h1.getNext()) {
            length1 ++;
            h1 = h1.getNext();
        }
        while(null != h2.getNext()) {
            length1 ++;
            h2 = h2.getNext();
        }
        return length1>=length2?getNode(h1,length1,h2,length2):getNode(h2,length2,h1,length1);
    }

这是最乐观的一种情况,但是还需要考虑链表上存在环的情况。那么还需要添加一个方法,判断链表上是否存在环。
对于如何判断链表上是否存在环,解决办法是采用快慢指针,两个指针P1、P2分别指向同一个链表的头节点,之后,P1一次前进两个节点,P2一次前进一个节点。如果最终P1和P2能重合,则说明一定存在交点。反之如果最终P1或者P2存在一个为空的情况,则说明这两个链表不相交。

/**
     * 判断是否存在环  
     * 步骤:设置两个指针同时指向head,其中一个一次前进一个节点(P1),另外一个一次前进两个节点(P2)。
     * p1和p2同时走,如果其中一个遇到null,则说明没有环,如果走了N步之后,二者指向地址相同,那么说明链表存在环。
     * @param h
     * @return
     */
    public static boolean isLoop(DataNode h) {
        DataNode p1 = h;
        DataNode p2 = h;
        while(p2.getNext() != null && p2.getNext().getNext()!=null){
            p1 = p1.getNext();
            p2 = p2.getNext().getNext();
            if(p1 == p2) 
                break;
        }
        return !(p1==null||p2==null);
    }

因此上述问题还有另外一个解法,将其中一个链表首尾相连,检测另外一个链表是否存在环,如果存在(该环就是首尾相连的链表),则两个链表相交,而检测出来的依赖环入口即为相交的第一个点。需找出环的入口,设置p1,p2两个指针,同样一个走一步一个走两步,两者相遇则必在环上某一点相遇,记下此位置p1=p2,在p1和p2重合后,设置一个p3指向表头,然后p1和p3每次同时行走一步,每步前进一个节点,等到p1和p3重合时,重合的位置就是环的入口。
参考csdn上的一张图:


Paste_Image.png

设L1为无环长度,L2为环长,a为两指针相遇时慢速指针在环上走过的距离,而且a一定小于环总长L2(这是因为当慢速指针刚进入环时,快速指针已经在环中,且距离慢速指针的距离最长为L2-1,需要追赶的距离为L2-1,即刚好在慢速指针的下一个节点,需要几乎一整圈的距离来追赶,赶上时,慢速指针也不能走完一圈)。此时设慢速指针走过的节点数为N,则可列出:
快速指针走过的节点数为: 2N = L1 + k * L2 + a; (这里快速指针走过的节点数一定是慢速指针走过的2倍)。
慢速指针走过的节点数为: N = L1 + a;
则相减可得, N = k * L2 , 于是得到 k * L2 = L1 + a; 即, L1 = (k-1) * L2 + (L2 - a) (这里k至少是大于等于1的,因为快速指针至少要多走一圈)
即 L1的长度 = 环长的整数倍 + 相遇点到入口点的距离, 此时设置头结点p3, 与p1同时,每次都走一步,相遇点即为入口点。

    /**
     *  方法二: 将其中一个链表首尾相连 从另外一个链表开始,检测是否存在环,如果存在,则说明二者相交。
     *  如果需要找出环的入口,则设P1 P2 两个指针,P1一次走两步,P2一次走一步,两者在环上某一点相遇。记下此位置。
     *  此时设置一个指针P3指向表头,然后P1和P3每次同时行走一步,每步前进一个节点。等到P1、P3重合时,则重合位置即使环入口。
     * @param h1
     * @param h2
     * @return
     */
    public  DataNode entryNoLoop(DataNode h1,DataNode h2) {
        DataNode p = h1;
        while(null != p.getNext()){
            p = p.getNext();
        }
        p.setNext(h1);
        return entryLoop(h2);
    }
    
    /**
     * 获取环的入口点
     * @param h
     * @return
     */
    public DataNode entryLoop(DataNode h) {
        DataNode p1 = h;
        DataNode p2 = h;
        DataNode p3 = h;
        
        while(null != p2.getNext() && null != p2.getNext().getNext()){
            p1 = p1.getNext();
            p2 = p2.getNext().getNext();
            if(p1 == p2)
                break;
        }
        while(p3 != p1) {
            p1 = p1.getNext();
            p3 = p3.getNext();
        }
        return p3;
    }
    

2.两个链表均存在环

对于连个链表均存在环的情况,相交点要么在环上,要么在环外。

Paste_Image.png

无论上述何种情况,均需要首先分别找到各自到环的入口点。解法可以即使上述entryLoop方法。
在得到环的入口点之后,各自判断环的入口点是否相同,如果如口点相同,则为左图描述情况,因此只需计算着两个链表到入口点部分长度之差,然后用长的部分减去差,再同时与短的部分同步前进,如果节点相同,则为相交点。反之如果入口点不同,则相交点为这两个链表的任意一个入口点。

/**
     * 
     * @param h1
     *            链表1的头节点
     * @param l1
     *            链表1的环入口
     * @param h2
     *            链表2的头节点
     * @param l2
     *            链表2的头节点
     * @return
     */
    public static DataNode bothLoop(DataNode h1, DataNode l1, DataNode h2, DataNode l2) {
        DataNode p1 = null;
        DataNode p2 = null;
        if (l1 == l2) {
            p1 = h1;
            p2 = h2;
            int n = 0;
            while (p1 != l1) {
                n++;
                p1 = p1.getNext();
            }
            while (p2 != l2) {
                n--;
                p2 = p2.getNext();
            }
            p1 = n > 0 ? h1 : h2;
            p2 = p1 == h1 ? h2 : h1;
            n = Math.abs(n);
            while (n != 0) {
                n--;
                h1 = h1.getNext();
            }
            while (p1 != p2) {
                p1 = p1.getNext();
                p2 = p2.getNext();
            }
            return p1;
        } else {
            p1 = l1.getNext();
            while (p1 != l1) {
                if (p1 == l2) {
                    return l1;
                }
            }
            return null;
        }
    }

    /**
     * 
     * @param h1
     * @param h2
     * @return
     */
    public DataNode getJoinNode(DataNode h1, DataNode h2) {
        if (null == h1 || null == h2)
            return null;
        DataNode l1 = entryLoop(h1);
        DataNode l2 = entryLoop(h2);
        if (null == l1 && null == l2)
            return getFirstJoinNode(h1, h2);
        if (null != l1 && null != l2)
            return bothLoop(h1,l1,h2,l2);
        return null;
    }

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,029评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,395评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,570评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,535评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,650评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,850评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,006评论 3 408
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,747评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,207评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,536评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,683评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,342评论 4 330
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,964评论 3 315
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,772评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,004评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,401评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,566评论 2 349

推荐阅读更多精彩内容