两个链表第一个公共子节点

这是一道经典的链表问题,剑指offer52 先看一下题目:输入两个链表,找出它们的第一个公共节点。例如下面的两个链表:

image.png

 方法一:哈希和集合

先将一个链表元素全部存到Map里,然后一边遍历第二个链表,一边检测Hash中是否存在当前结点,如果有交点,那么一定能检测出来。

代码如下:

/**
 * 使用集合查找两个链表的第一个公共节点。
 * @param headA 第一个链表的头节点。
 * @param headB 第二个链表的头节点
 * @return 第一个公共节点,如果不存在公共节点则返回null。
 */
public ListNode findFirstCommonNodeBySet(ListNode headA, ListNode headB) {
    // 创建一个集合来存储链表A的节点
    Set<ListNode> set = new HashSet<>();

    // 遍历链表A,将每个节点添加到集合中
    while (headA != null) {
        set.add(headA);
        headA = headA.next;
    }

    // 遍历链表B,检查每个节点是否存在于集合中
    while (headB != null) {
        if (set.contains(headB))
            return headB; // 找到第一个公共节点
        headB = headB.next;
    }

    return null; // 未找到公共节点
}

第二种方式:使用栈

当给定以下两个链表作为输入时:

链表A: 1 -> 2 -> 3 -> 4 -> 5 -> 6
链表B: 9 -> 8 -> 5 -> 6

在这个例子中,链表A和链表B在节点5和节点6处相交。

现在我们将遍历这两个链表,并执行给定的代码。

首先,我们将链表A的节点逐个入栈 stackA 中,栈顶元素位于链表的末尾:

stackA: 6 -> 5 -> 4 -> 3 -> 2 -> 1

然后,我们将链表B的节点逐个入栈 stackB 中,栈顶元素位于链表的末尾:

stackB: 6 -> 5 -> 8 -> 9

接下来,我们开始循环遍历。在第一次迭代中,我们比较 stackA 的栈顶元素6和 stackB 的栈顶元素6,它们相等。我们从 stackA 中弹出栈顶元素6,并将其赋值给 preNode,表示我们找到了第一个公共节点。

stackA: 5 -> 4 -> 3 -> 2 -> 1
stackB: 5 -> 8 -> 9
preNode: 6

然后,我们从 stackB 中弹出栈顶元素6,因为链表的公共节点必须在这之前的位置。

stackA: 5 -> 4 -> 3 -> 2 -> 1
stackB: 8 -> 9
preNode: 6

在第二次迭代中,我们比较 stackA 的栈顶元素5和 stackB 的栈顶元素8,它们不相等。于是我们跳出循环,表示找到了链表的分叉点。

因此,我们最终得到的 preNode 是节点6,它是链表A和链表B的第一个公共节点。

import java.util.Stack;

/**
 * 使用栈查找两个链表的第一个公共节点。
 * @param headA 第一个链表的头节点。
 * @param headB 第二个链表的头节点
 * @return 第一个公共节点,如果不存在公共节点则返回null。
 */
public ListNode findFirstCommonNodeByStack(ListNode headA, ListNode headB) {
    // 创建两个栈,分别用于存储链表A和链表B的节点
    Stack<ListNode> stackA = new Stack<>();
    Stack<ListNode> stackB = new Stack<>();
    
    // 遍历链表A,将每个节点压入栈中
    while (headA != null) {
        stackA.push(headA);
        headA = headA.next;
    }
    
    // 遍历链表B,将每个节点压入栈中
    while (headB != null) {
        stackB.push(headB);
        headB = headB.next;
    }
    
    // 从栈顶开始比较两个栈的节点,找到第一个不相等的节点即为分叉点,其前一个节点即为第一个公共节点
    ListNode preNode = null;
    while (!stackA.isEmpty() && !stackB.isEmpty()) {
        if (stackA.peek() == stackB.peek()) {
            preNode = stackA.pop();
            stackB.pop();
        } else {
            break;
        }
    }
    
    return preNode;
}

第三种方法:拼接两个字符串:

先看下面的链表A和B:
A: 0-1-2-3-4-5
B: a-b-4-5
如果分别拼接成AB和BA会怎么样呢?
AB:0-1-2-3-4-5-a-b-4-5
BA:a-b-4-5-0-1-2-3-4-5
我们发现拼接后从最后的4开始,两个链表是一样的了,自然4就是要找的节点,所以可以通过拼接的方式来寻找交点。这么做的道理是什么呢?我们可以从几何的角度来分析。我们假定A和B有相交的位置,以交点为中心,可以将两个链表分别分为left_a和right_a,left_b和right_b这样四个部分,并且right_a和right_b是一样的,这时候我们拼接AB和BA就是这样的结构:

image.png


我们说right_a和right_b是一样的,那这时候分别遍历AB和BA是不是从某个位置开始恰好就找到了相交的点了?

public ListNode findFirstCommonNode(ListNode pHead1, ListNode pHead2) {
    // 检查输入的参数是否为空
    if (pHead1 == null || pHead2 == null) {
        return null;
    }
    
    // 创建两个指针,分别指向链表 pHead1 和 pHead2 的头节点
    ListNode p1 = pHead1;
    ListNode p2 = pHead2;
    
    // 循环遍历链表,直到找到公共节点或到达链表末尾
    while (p1 != p2) {
        // 将指针向前移动到下一个节点
        p1 = p1.next;
        p2 = p2.next;
        
        // 检查指针是否为空,并继续遍历另外一个链表
        if (p1 != p2) {//这里起到的作用是排除p1和p2同时为空的情况,避免造成死循环
            // 如果链表 p1 已经遍历完,则将其指向链表 pHead2 的头节点
            if (p1 == null) {
                p1 = pHead2;
            }
            
            // 如果链表 p2 已经遍历完,则将其指向链表 pHead1 的头节点
            if (p2 == null) {
                p2 = pHead1;
            }
        }
    }
    
    // 返回公共节点或 null(如果没有公共节点)
    return p1;
}

第四种方法:差和双指针

我们再看另一个使用差和双指针来解决问题的方法。假如公共子节点一定存在第一轮遍历,假设La长度为L1,Lb长度为L2.则|L2-L1|就是两个的差值。第二轮遍历,长的先走|L2-L1|,然后两个链表同时向前走,结点一样的时候就是公共结点了。

public ListNode findFirstCommonNode(ListNode pHead1, ListNode pHead2) {
     if(pHead1==null || pHead2==null){
             return null;
         }
        ListNode current1=pHead1;
        ListNode current2=pHead2;
        int l1=0,l2=0;
        //分别统计两个链表的长度
        while(current1!=null){
            current1=current1.next;
            l1++;
        }
        
         while(current2!=null){
            current2=current2.next;
            l2++;
        }
//将current的值变回头节点
        current1=pHead1;
        current2=pHead2;
        int sub=l1>l2?l1-l2:l2-l1;
        //长的先走sub步
       if(l1>l2){
           int a=0;
           while(a<sub){
            current1=current1.next;
            a++;
        }   
       }
      
       if(l1<l2){
           int a=0;
           while(a<sub){
            current2=current2.next;
            a++;
        }   
       }
        //同时遍历两个链表,当值相当的时候就可以返回节点了
       while(current2!=current1){
          current2=current2.next;
          current1=current1.next;
       } 
        
        return current1;
    }

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
链表是一种数据结构,它由节点组成,每个节点包含两部分:数据和指向下一个节点的指针。链表中的节点按照一定的顺序排列,并通过指针相互连接起来,形成一个链式结构。链表的一个重要特点是不需要连续的内存空间,因此可以动态分配内存,这使得链表非常适合处理插入和删除节点这样的操作。 与链表相比,二叉树是一种更复杂的数据结构,它由节点和指向子节点的指针组成。与链表不同的是,在二叉树中每个节点最多有两个子节点,左子节点和右子节点。二叉树有多种不同的变种,包括二叉搜索树、AVL树、红黑树等。 在本关中,你需要建立一个带头结点的单向链表,这意味着在链表的开头添加一个特殊的头结点,它不包含任何数据,但是包含指向链表第一个实际节点的指针。头结点的作用是方便对链表的操作,例如插入、删除、遍历等。你需要熟悉链表的基本操作,例如创建、插入、删除、查找、反转等,并能够灵活运用这些操作来实现本关的任务。 在C语言中,链表通常使用结构体来表示节点,例如: ``` struct ListNode { int val; struct ListNode *next; }; ``` 其中val用来保存节点的数据,next用来指向下一个节点。通过结构体指针可以访问节点的成员变量,例如: ``` struct ListNode *node = malloc(sizeof(struct ListNode)); node->val = 1; node->next = NULL; ``` 这段代码创建了一个节点,赋值为1,next指针为NULL,表示这是链表的最后一个节点。对于带头结点的链表,可以用类似的方式定义头结点: ``` struct ListNode *head = malloc(sizeof(struct ListNode)); head->next = NULL; ``` 这段代码创建了一个头结点,next指针为NULL,表示这是一个空链表。注意,在访问链表节点时,需要先检查指针是否为NULL,以防止访问空指针导致程序崩溃。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值