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

leetcode题号:剑指 Offer 52.

这道题解法比较多样,而且涉及到几种解链表题目常用的方法。

题目:

题意:需要找到两个链表的 第一个公共节点

比如 nodeA = 1->3->4->5->9nodeB = 2->6->5->9

那么这两个链表的 第一个公共节点就是 5->9

1.1 Hash

思路:先将 head1链表元素全部放进Set 集合里,然后一边遍历 head2链表,一边检查 Set中是否有已经存在的节点,调用 contains方法,如果为 ture就代表找到了,返回该节点。

 代码:

public static ListNode byHashMap(ListNode head1, ListNode head2) {
        if (head1 == null || head2 == null) {
            return null;
        }
        // 保存第一个所有节点的信息
        HashSet<ListNode> set = new HashSet<>();
        // 先把 head1 的所有数据放进 map
        while (head1 != null) {
            set.add(head1);
            head1 = head1.next;
        }

        // 将 head2 数据与 head1 对比,看是否包含有
        while (head2 != null) {
            if (set.contains(head2)) {
                return head2;
            }
            head2 = head2.next;
        }

        // 执行到最后发现没有公共节点
        return null;
    }

1.2 使用栈

思路:需要两个栈,分别保存两个链表的信息,链表进栈后,栈顶数据就是链表尾部,正好可以用来判断是否有公共子节点,

还有一点要注意的是 要找到最晚出栈的那一组返回,根据题意要找到最先公共的节点

这种方式需要两个 O(n) 的空间

 代码:

public static ListNode byStack(ListNode head1,ListNode head2) {
        if (head1 == null || head2 == null) {
            return null;
        }

        // 初始化两个 栈
        Stack<ListNode> stack1 = new Stack<>();
        Stack<ListNode> stack2 = new Stack<>();

        // 将两个链表分别存进两个栈中
        while (head1 != null) {
            stack1.push(head1);
            head1 = head1.next;
        }
        while (head2 != null) {
            stack2.push(head2);
            head2 = head2.next;
        }

        // 用来记录结果
        ListNode result = null;

        // 对 栈顶进行比较,栈顶也就是链表的尾部
        while (stack1.size() > 0 && stack2.size() > 0) {
            if (stack1.peek() == stack2.peek()) {
                result = stack1.pop();
                stack2.pop();
            } else {
                break;
            }
        }
        return result;
    }

1.3 字符拼接法

两个链表进行拼接(比较节点)

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

B : 6 -> 8 -> 4 -> 5-> 6

把它们分别拼在对方的尾部,此时就变成

A : 1 -> 2 -> 3 -> 4 -> 5 -> 6 -> 6 -> 8 -> 4 -> 5 -> 6

B : 6 -> 8 -> 4 -> 5 -> 6 -> 1-> 2 -> 3 -> 4 -> 5 -> 6

此时它们的长度刚好一致,发现两个链表最后面 从 4 开始,刚好是一样的,这时就能找到第一个公共子节点了

 但是这样还是有不行的,因为在源码中是这样 构建  公共字节的点的,看看是如何初始化的。

初始化两个链表

 private static ListNode[] initLinkedList() {
        ListNode[] heads = new ListNode[2];
//        构造第一个链表交点之前的元素 1 ->2-> 3
        heads[0] = new ListNode(1);
        ListNode current1 = heads[0];
        current1.next = new ListNode(2);
        current1 = current1.next;
        current1.next = new ListNode(3);
        current1 = current1.next;
//        11->22
//        构造第二个链表交点之前的元素
        heads[1] = new ListNode(11);
        ListNode current2 = heads[1];
        current2.next = new ListNode(22);
        current2 = current2.next;
        
//        构造公共交点以及之后的元素
        ListNode node4 = new ListNode(4);
        current1.next = node4;
        current2.next = node4;
        ListNode node5 = new ListNode(5);
        node4.next = node5;
        
        ListNode node6 = new ListNode(6);
        node5.next = node6;

        return heads;
    }

注意看上面初始化两个链表的源码中, 在最后初始化 node4 节点的时候,current1current2 这两个链表的next都是指向 node4的,证明它们公共部分的 引用地址是一样的,所以这就是在比较地址 

在 deBug 的时候也是发现了这个问题,两个链表 后面公共部分的节点,引用地址都是一样的

此方式实现代码

public static ListNode byCombine(ListNode head1,ListNode head2) {
        // 进行判空
        if (head1 == null || head2 == null) {
            return null;
        }

        // 分别定义两个节点用来遍历
        ListNode p1 = head1;
        ListNode p2 = head2;

        /**
         * 比较两个节点的地址
         * 如果相同的话就可以返回了(随便返回哪个,反正都相同)
         */
        while (p1 != p2) {
            // 不相同则都往后移
            p1 = p1.next;
            p2 = p2.next;

            // 如果两个链表遍历完还是空,则没有交点
            if (p1 == null && p2 == null) {
                return null;
            }

            // 如果为空,则指向另一个链表的头节点
            if (p1 == null) {
                p1 = head2;
            }
            if (p2 == null) {
                p2 = head1;
            }
        }

        return p1;
    }

在这个方法中,有一个致命bug,就是修改了当前公共节点的 值,还是会被识别出这是公共子节点,因为这种方式是根据 引用地址比较的。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值