算法通关村第一关——链表经典问题之两链表第一个公共子节点问题

本文介绍了四种方法来找出两个链表相交的第一个节点:1)使用哈希表/HashSet存储一个链表的所有节点,再遍历另一个链表查找公共节点;2)通过HashSet辅助查找;3)借助栈,将两个链表元素分别压栈,同步出栈找到相同元素;4)通过计算链表长度差,然后同步遍历找到交点。这些方法涉及数据结构和算法的应用。
摘要由CSDN通过智能技术生成

该题目为 剑指 Offer 52.

输入两个链表,找出它们的第一个公共节点。

如下面的两个链表:

在节点 c1 开始相交。

示例 1:

输入:intersectVal = 8, listA = [4,1,8,4,5], listB = [5,0,1,8,4,5], skipA = 2, skipB = 3
输出:Reference of the node with value = 8
输入解释:相交节点的值为 8 (注意,如果两个列表相交则不能为 0)。从各自的表头开始算起,链表 A 为 [4,1,8,4,5],链表 B 为 [5,0,1,8,4,5]。在 A 中,相交节点前有 2 个节点;在 B 中,相交节点前有 3 个节点。

本文提供4种不同方法进行问题分析

方法一(使用Hash进行查找)

先将其中一个链表List1进行遍历,保存到Map中,然后再从头到尾遍历另一个链表List2的元素,逐个判断其元素是否存在于Map中,若对应元素存在于Map中,则该元素为第一个公共子节点。

(另外,由于可以使用Hash进行解题,相同的,集合中的HashSet也可以实现,具体步骤与Hash相似)

代码实现如下:

//通过Hash辅助查找
public static ListNode findFirstCommonNodeByMap(ListNode pHead1, ListNode pHead2) {
        HashMap<ListNode, Integer> listNodeHashMap = new HashMap<>();
        if (pHead1 == null || pHead2 == null){
            return null;
        }

        ListNode current1 = pHead1;
        while (current1 != null){
            listNodeHashMap.put(current1,null);
            current1 = current1.next;
        }

        ListNode current2 = pHead2;
        while (current2 != null){
            if (listNodeHashMap.containsKey(current2)){
                return current2;
            }
            current2 = current2.next;
        }
        return null;
    }


//方法2:通过集合辅助查找
public static ListNode findFirstCommonNodeBySet(ListNode headA, ListNode headB) {
        Set<ListNode> set = new HashSet<>();
        ListNode current1 = headA;
        ListNode current2 = headB;

        while (current1 != null){
            set.add(current1);
            current1 = current1.next;
        }

        while (current2 != null){
            if (set.contains(current2)){
                return current2;
            }
            current2 = current2.next;
        }
        return null;
    }

方法二(通过栈辅助查找)

可以将两个链表的元素全分别压入两个不同的栈中,再同时出栈,如果其对应出栈的元素相同,就继续出栈,一直找到最晚出栈的那一组(即对应的下一组的栈顶部元素不相同的一组)

代码实现如下:

public static ListNode findFirstCommonNodeByStack(ListNode headA, ListNode headB) {
        Stack<ListNode> stack1 = new Stack<>();
        Stack<ListNode> stack2 = new Stack<>();
        while (headA != null){
            stack1.push(headA);
            headA = headA.next;
        }
        while (headB != null){
            stack2.push(headB);
            headB = headB.next;
        }
        ListNode preNode = null;
        while (stack1.size() != 0 && stack2.size() != 0){
            if (stack1.peek() == stack2.peek()){
                preNode = stack1.pop();
                stack2.pop();
            } else {
                break;
            }
        }
        return preNode;
    }

但是时间复杂度为两个O(n),面试不占优。

方法三(通过序列拼接):

先看看下面的链表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就是这样的结构:

 由于right_a和right_b一样,所以这时候只要分别遍历AB和BA直到到相同的元素的位置就是要找的交点了。

另外,这里还可以进行进一步的优化,建立新链表太浪费空间了,我们只要在每个链表访问完后将链表指针指向另一个链表继续进行遍历即可。

 public static ListNode findFirstCommonNodeByCombine(ListNode pHead1, ListNode pHead2) {
       if (pHead1 == null || pHead2 == null){
           return null;
       }
       ListNode p1 = pHead1;
       ListNode p2 = pHead2;
       while (p1 != p2){
           p1 = p1.next;
           p2 = p2.next;
           if (p1 == null){
               p1 = pHead2;
           } else if (p2 == null){
               p2 = pHead1;
           }
       }
       return p1;
    }

方法四:通过差值来实现

先遍历两个链表求分别求出它们对应的长度l1,l2,然后让较长的链表先遍历|l1-l2|长度,然后再让两个链表同时遍历,并且比较它们对应元素的值,若对应元素相同,则该元素为交点。

代码实现:

public static ListNode findFirstCommonNodeBySub(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){
           l1++;
           current1 = current1.next;
       }
       while (current2 != null){
           l2++;
           current2 = current2.next;
       }

       current1 = pHead1;
       current2 = pHead2;
       while (l1 != l2){
           if (l1 > l2){
               l1--;
               current1 = current1.next;
           }
           if (l2 > l1){
               l2--;1
               current2 = current2.next;
           }
       }
       while (current1 != current2){
           current1 = current1.next;
           current2 = current2.next;
       }
       return current1;
    }

总结:

方法一、二更多的是使用常用的数据结构及其所提供的的方法,比较容易想到,也应该是大多数人会使用的方法。

然后方法三、四更多在于思维层面的思考,只要逻辑正确代码还是比较容易实现的。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值