算法通关村第一关——链表第一个公共子节点

1 四种方法解决两个链表第一个公共子节点

        两个链表的头结点都是已知的,相交之后成为一个单链表,但是相交的位置未知,并且相交之前的结点数也是未知的,请设计算法找到两个链表的合并点。

        将常用的数据结构和算法常用思想都想一遍;常用的数据结构有数组,链表,队列,栈,Hash,集合,数,堆。常用的算法思想有查找,排序,双指针,递归,迭代,分治,贪心,回溯和动态规划等

        首先想到的方法是蛮力法,类似于冒泡排序的方式,将第一个链表中的每一个结点依次与第二个链表进行比较,当出现相等的结点指针时,即为相交结点。虽然简单,但是时间复杂度高,排除!

        再看Hash,先将第一个链表元素全部存到 Map 里,然后一边遍历第二个链表,一边检测当前元素是否在 Hash 中,如果两个链表有交点,那就找到了。既然 Hash 可以,那么集合也能解决。还可以使用栈,将两个链表分别压到两个栈里,之后一边同时出栈,一边比较出栈元素是否一致,如果一致则说明存在相交。然后继续找,最晚出栈的那组一致的结点就是要找的位置。

综上我们得到了四种方法。

1.1 哈希和集合

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

    /**
     * 哈希和集合
     * @param headA
     * @param headB
     * @return
     */
    public Node findFirstCommonNodeBySet(Node headA,Node headB){
        HashSet<Node> set = new HashSet<>();
        while(headA != null){
            set.add(headA);
            headA = headA.next;
        }
        while(headB != null){
            if (set.contains(headB)){
                return headB;
            }
            headB = headB.next;
        }
        return null;
    }

1.2 使用栈

        这里需要使用两个栈,分别将两个链表的结点入两个栈,然后分别出栈,如果相等就继续出栈,一直找到最晚出栈的那一组。这种方式需要两个O(n)的空间。

    /**
     * 使用栈
     * @param headA
     * @param headB
     * @return
     */
    public Node findFirstCommonNodeByStack(Node headA,Node headB){
        Stack<Node> stackA = new Stack<>();
        Stack<Node> stackB = new Stack<>();
        while(headA != null){
            stackA.push(headA);
            headA = headA.next;
        }
        while(headB != null){
            stackB.push(headB);
            headB = headB.next;
        }
        Node preNode = null;
        while(stackA.size() > 0 && stackB.size() > 0){
            if (stackA.peek() == stackB.peek()){
                preNode = stackA.pop();
                stackB.pop();
            }else {
                break;
            }
        }
        return preNode;
    }

1.3 拼接两个字符串

先看下面两个链表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

AB : 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就是这样的结构:

 

        这里还可以进一步优化,建立新的链表太浪费空间,我们只要在每个链表访问完了之后,调整到下一链表的表头继续遍历就行了。

    public Node findFirstCommonNode(Node pHead1,Node pHead2){
        if (pHead1 == null || pHead2 == null){
            return null;
        }
        Node p1 = pHead1;
        Node p2 = pHead2;
        while(p1 != p2){
            p1 = p1.next;
            p2 = p2.next;
            if (p1 != p2){
                //一个链表访问完了就跳转到另一个链表继续访问
                if (p1 == null){
                    p1 = pHead2;
                }
                if (p2 == null){
                    p2 = pHead1;
                }
            }
        }
        return p1;
    }

1.4 差和双指针

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

    public Node findFirstCommonNode1(Node pHead1,Node pHead2){
        if (pHead1 == null || pHead2 == null){
            return null;
        }
        Node current1 = pHead1;
        Node current2 = pHead2;
        int l1 = 0,l2 = 0;
        //分别统计两个链表的长度
        while(current1 != null){
            current1 = current1.next;
            l1++;
        }
        while(current2 != null){
            current2 = current2.next;
            l2++;
        }
        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 (l2 > l1){
            int a = 0;
            while (a < sub){
                current2 = current2.next;
                a++;
            }
        }
        //同时遍历两个链表
        while(current2 != current1){
            current2 = current2.next;
            current1 = current1.next;
        }
        return current1;
    }

  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值