如何判断两个链表是否相交并找出交点

解题思路

情况1:两个链表均不含有环

1、直接法
采用暴力的方法,遍历两个链表,判断第一个链表的每个结点是否在第二个链表中,时间复杂度为O(len1*len2),耗时很大。

2、hash计数法
如 果 两个链表相交,则两个链表就会有共同的结点;而结点地址又是结点唯一标识。因而判断两个链表中是否存在地址一致的节点,就可以知道是否相交了。可以对第一 个链表的节点地址进行hash排序,建立hash表,然后针对第二个链表的每个节点的地址查询hash表,如果它在hash表中出现,则说明两个链表有共 同的结点。这个方法的时间复杂度为:O(max(len1+len2);但同时还得增加O(len1)的存储空间存储哈希表。这样减少了时间复杂度,增加 了存储空间。

以链表节点地址为值,遍历第一个链表,使用Hash保存所有节点地址值,结束条件为到最后一个节点(无环)或Hash中该地址值已经存在(有环)。

再遍历第二个链表,判断节点地址值是否已经存在于上面创建的Hash表中。

这个方面可以解决题目中的所有情况,时间复杂度为O(m+n),m和n分别是两个链表中节点数量。由于节点地址指针就是一个整型,假设链表都是在堆中动态创建的,可以使用堆的起始地址作为偏移量,以地址减去这个偏移量作为Hash函数

3、转换为环的问题
第三种思路是比较奇特的,在编程之美上看到的。先遍历第一个链表到他的尾部,然后将尾部的next指针指向第二个链表(尾部指针的next本来指向的是null)。这样两个链表就合成了一个链表,判断原来的两个链表是否相交也就转变成了判断新的链表是否有环的问题了:即判断单链表是否有环?

这样进行转换后就可以从链表头部进行判断了,其实并不用。通过简单的了解我们就很容易知道,如果新链表是有环的,那么原来第二个链表的头部一定在环上。因此我们就可以从第二个链表的头部进行遍历的,从而减少了时间复杂度(减少的时间复杂度是第一个链表的长度)。这种方法可以判断两个链表是否相交,但不太容易找出他们的交点。

4、进一步考虑“如果两个没有环的链表相交于某一节点,那么在这个节点之后的所有节点都是两个链表共有的”这个特点,我们可以知道,如果它们相交,则最后一个节点一定是共有的。而我们很容易能得到链表的最后一个节点,所以这成了我们简化解法的一个主要突破口。那么,我们只要判断两个链表的尾指针是否相等。相等,则链表相交;否则,链表不相交。

注意:两个不含环的单链表的相交,相交指的是结点的地址相同,而不是结点的值相同

情况2:链表有环时
1:假设两个链表都有环,情况只有2种
相交于”环上”或相交于”不是环的部分”。因此环一定是在公共部分上的。假如知道其中一个链表上环的任意一个节点,则只需要判断是否在另一个链表上就行了

2:当一个链表中有环,一个链表中没有环时,两个链表必不相交

代码实现

建立在这两篇博客的基础上
1:如何实现链表
2:如何知道链表是否有环

public function detect($linkList1, $linkList2) {
    $p1 = $linkList1->head;
    $p2 = $linkList2->head;

    $size1 = $linkList1->size;
    $size2 = $linkList2->size;

    $isLoop1 = false;
    $isLoop2 = false;
    $circleNode1 = null;
    $circleNode2 = null;

    //此处使用引用
    $linkList1->isLoopLinkListByMap($isLoop1, $circleNode1);
    $linkList2->isLoopLinkListByMap($isLoop2, $circleNode2);

    //一个有环 一个无环
    if($isLoop1 != $isLoop2){
        return false;
    }
    //两个都无环,判断最后一个节点是否相等
    elseif(!$isLoop1 && !$isLoop2){
        for($i = 0; $i< $size1; $i++){
            $p1 = $p1->next;
        }

        for($j = 0; $j< $size2; $j++){
            $p2 = $p2->next;
        }

        if($p1 != $p2){
            return null;
        }
        return true;
    }
    //两个都有环,判断环里的节点是否能到达另一个链表环里的节点 
    else{
        while($circleNode1 != null){
            if($circleNode1 == $circleNode2){
                return true;
            }
            $circleNode1 = $circleNode1->next;
        }
        return false;
    }
    return false;
}

扩展知识

求相交的第一个元素

function findNode($linkList1, $linkList2) {
    $p1 = $linkList1->head;
    $p2 = $linkList2->head;
    $diff = 0;
    if($p1 == null || $p2 == null) {
        return null;
    }

    $diff = abs($linkList1->size - $linkList2->size);
    if($linkList1->size > $linkList2->size){
        $p1 = $linkList1->head;
        $p2 = $linkList2->head;
    }
    else {
        $p2 = $linkList1->head;
        $p1 = $linkList2->head;
    }

    for($i = 0; $i < $diff; $i++){
        $p1 = $p1->next;
    }

    while($p1 != null) {
        if($p1 == $p2) {
            return $p1;
        }
        $p1 = $p1->next;
        $p2 = $p2->next;

    }

    return null;
}
#include <stdio.h> #include <stdlib.h> //定义链表结构体 struct Node { int data; struct Node *next; }; //创建链表函数 struct Node *createList(int n) { struct Node *head, *p, *q; int i, num; head = (struct Node *)malloc(sizeof(struct Node)); head->next = NULL; q = head; for (i = 0; i < n; i++) { printf("请输入第%d个节的值:", i + 1); scanf("%d", &num); p = (struct Node *)malloc(sizeof(struct Node)); p->data = num; p->next = NULL; q->next = p; q = p; } return head; } //输出链表函数 void printList(struct Node *head) { struct Node *p; p = head->next; while (p != NULL) { printf("%d ", p->data); p = p->next; } printf("\n"); } //逆置链表函数 void reverseList(struct Node *head) { struct Node *p, *q, *r; p = head->next; q = NULL; while (p != NULL) { r = p->next; p->next = q; q = p; p = r; } head->next = q; } //找中间节函数 struct Node *findMiddleNode(struct Node *head) { struct Node *p, *q; p = head->next; q = head->next; while (q != NULL && q->next != NULL) { p = p->next; q = q->next->next; } return p; } //找倒数第k个节函数 struct Node *findKthNode(struct Node *head, int k) { struct Node *p, *q; p = head->next; q = head->next; while (k > 0 && q != NULL) { q = q->next; k--; } if (k > 0) return NULL; while (q != NULL) { p = p->next; q = q->next; } return p; } //删除倒数第k个节函数 void deleteKthNode(struct Node *head, int k) { struct Node *p, *q; p = head; q = head->next; while (k > 0 && q != NULL) { p = q; q = q->next; k--; } if (k > 0) return; p->next = q->next; free(q); } //判断链表是否有环函数 int hasCycle(struct Node *head) { struct Node *p, *q; p = head; q = head; while (q != NULL && q->next != NULL) { p = p->next; q = q->next->next; if (p == q) return 1; } return 0; } //找出环的交点函数 struct Node *findCycleNode(struct Node *head) { struct Node *p, *q; int flag = 0; p = head; q = head; while (q != NULL && q->next != NULL) { p = p->next; q = q->next->next; if (p == q) { flag = 1; break; } } if (flag == 0) return NULL; p = head; while (p != q) { p = p->next; q = q->next; } return p; } //判断两个链表是否相交函数 int isIntersect(struct Node *head1, struct Node *head2) { struct Node *p, *q; p = head1; q = head2; while (p->next != NULL) { p = p->next; } while (q->next != NULL) { q = q->next; } if (p == q) return 1; else return 0; } int main() { struct Node *head1, *head2, *p; int n, k; printf("请输入链表1的节个数:"); scanf("%d", &n); head1 = createList(n); printf("链表1为:"); printList(head1); reverseList(head1); printf("逆置后的链表1为:"); printList(head1); p = findMiddleNode(head1); printf("链表1的中间节为:%d\n", p->data); printf("请输入链表1的倒数第k个节:"); scanf("%d", &k); p = findKthNode(head1, k); printf("链表1的倒数第%d个节为:%d\n", k, p->data); deleteKthNode(head1, k); printf("删除倒数第%d个节后的链表1为:", k); printList(head1); if (hasCycle(head1)) { p = findCycleNode(head1); printf("链表1有环,环的交点为:%d\n", p->data); } else { printf("链表1没有环\n"); } printf("请输入链表2的节个数:"); scanf("%d", &n); head2 = createList(n); printf("链表2为:"); printList(head2); if (isIntersect(head1, head2)) { printf("链表1和链表2相交\n"); p = findCycleNode(head1); printf("链表1和链表2的交点为:%d\n", p->data); } else { printf("链表1和链表2不相交\n"); } return 0; }
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值