编程导航算法通关村第一关|白银挑战|链表经典问题之两个链表第一个公共子节点

写在前面

这个题算比较经典的一道题,现在用5种方法来解决这道算法题,全部代码附在文末,包括测试用例,需要自取。

方法1:通过Hash辅助查找

public static ListNode findFirstCommonNodeByMap(ListNode pHead1, ListNode pHead2) {
        //warning 忘记判空了
        if (pHead1 == null || pHead2 == null) {
            return null;
        }
        Map<ListNode, Integer> map = new HashMap<>();
        ListNode p1 = pHead1;
        ListNode p2 = pHead2;
        //将第一个链表的节点依次放入map
        while (p1 != null) {
            map.put(p1, null);
            p1 = p1.next;
        }

        while (p2 != null) {
            if (map.containsKey(p2)) {
                return p2;
            }
            p2 = p2.next;
        }
        return null;
    }

方法2:通过集合来辅助查找

public static ListNode findFirstCommonNodeBySet(ListNode headA, ListNode headB) {
        ListNode h1 = headA;
        ListNode h2 = headB;

        List<ListNode> list = new ArrayList<>();
        while (h1 != null) {
            list.add(h1);
            h1 = h1.next;
        }

        while (h2 != null) {
            if (list.contains(h2)) {
                return h2;
            }
            h2 = h2.next;
        }
        return null;
    }

方法3:通过栈

public static ListNode findFirstCommonNodeByStack(ListNode headA, ListNode headB) {
        Stack<ListNode> h1 = new Stack<>();
        Stack<ListNode> h2 = new Stack<>();

        ListNode ha = headA;//因为Java是值传递,所以不会改变外部头节点的取值,这2行代码去掉也无妨
        ListNode hb = headB;

        while (ha != null) {
            h1.push(ha);
            ha = ha.next;
        }
        while (hb != null) {
            h2.push(hb);
            hb = hb.next;
        }

//        while (!h1.empty()) {
//            ListNode temp1 = h1.pop();
//            ListNode temp2 = h2.pop();
//            if (temp1 == temp2) {
//                ListNode temp3 = h1.pop();
//                ListNode temp4 = h2.pop();
//                if (temp3 != temp4) {
//                    return temp1;
//                }
//            }
//        }

        //优化
        //1、用一个变量保存上一个节点
        //2、增加判断2个栈大小的逻辑,过滤空栈
//        ListNode pre = null;
//        while(h1.size() > 0 && h2.size() > 0){
//            if(h1.peek() == h2.peek()){
                pre = h1.peek();
                h1.pop();
                h2.pop();
//                //优化
//                pre = h1.pop();
//                h2.pop();
//                if(h1.peek() != h2.peek()){
//                    return pre;
//                }
//            }else {
//                return null;
//            }
//        }
//        return null;

        //还能优化
        ListNode pre = null;
        while (h1.size() > 0 && h2.size() > 0) {
            if (h1.peek() == h2.peek()) {
                //优化
                pre = h1.pop();
                h2.pop();
            } else {
                break;
            }
        }
        return pre;
    }

方法4:通过序列拼接

public static ListNode findFirstCommonNodeByCombine(ListNode pHead1, ListNode pHead2) {
        //双指针法
        //算法思想:如果有A、B,2个链表,拼接成AB,BA,如果AB有公共节点,那肯定是唯一公共节点(由单链表定义可知)
        //所以一直遍历到相同的公共节点,就是要找的第一个公共节点

        //判空
        if (pHead1 == null && pHead2 == null) {
            return null;
        }

        ListNode p1 = pHead1;
        ListNode p2 = pHead2;

        while (p1 != p2) {
            //这里的p1 != p2不能替换成true,否则遇到没有公共节点的情况会死循环
            p1 = p1.next;
            p2 = p2.next;
            if (p1 != p2) {
                if (p1 == null) {
                    p1 = pHead2;
                }

                if (p2 == null) {
                    p2 = pHead1;
                }
            }
        }
        return p1;
    }

方法5:通过差值来实现

public static ListNode findFirstCommonNodeBySub(ListNode pHead1, ListNode pHead2) {
        //虽然代码看起来比之前的几种方法多了不少,但思路也比较简单,很巧妙
        //算法思想:首先遍历一次,获取2个链表的长度,然后再次遍历,第二次遍历,先让长的链表先走长度差值的距离
        //然后2个链表再一起往下走,最后相等的第一个节点,就是要找的节点
        //有一种对齐的思想在里面

        //判空
        if (pHead1 == null && pHead2 == null) {
            return null;
        }

        ListNode p1 = pHead1;
        ListNode p2 = pHead2;

        int lengthP1 = 0;
        int lengthP2 = 0;

        //分别获取2个链表的长度
        while (p1 != null) {
            p1 = p1.next;
            lengthP1++;
        }

        while (p2 != null) {
            p2 = p2.next;
            lengthP2++;
        }

        //计算差值,确保结果为绝对值
        int sub = lengthP1 > lengthP2 ? lengthP1 - lengthP2 : lengthP2 - lengthP1;

        p1 = pHead1;
        p2 = pHead2;
        //再次进行遍历

        if (lengthP1 > lengthP2) {
            while (sub > 0) {
                p1 = p1.next;
                sub--;
            }
        } else {
            while (sub > 0) {
                p2 = p2.next;
                sub--;
            }
        }

        while (p1 != p2) {
            p1 = p1.next;
            p2 = p2.next;
        }
        return p1;
    }

全部代码

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Stack;

public class FindFirstCommonNode {
    public static void main(String[] args) {
        ListNode[] listNodes = initLinkedList();

        System.out.println(toString(listNodes[0]));
        System.out.println(toString(listNodes[1]));

        ListNode firstCommonNode = findFirstCommonNodeByMap(listNodes[0], listNodes[1]);
        ListNode firstCommonNode2 = findFirstCommonNodeBySet(null, listNodes[1]);
        ListNode stackNode = findFirstCommonNodeByStack(listNodes[0], listNodes[1]);
        ListNode combine = findFirstCommonNodeByCombine(listNodes[0], listNodes[1]);
        ListNode sub = findFirstCommonNodeBySub(listNodes[0], listNodes[1]);

//        System.out.println(toString(firstCommonNode));
//        System.out.println(toString(firstCommonNode2));
//        System.out.println(toString(stackNode));
//        System.out.println(toString(combine));
        System.out.println(toString(sub));

    }

    /**
     * 方法1:通过Hash辅助查找
     *
     * @param pHead1
     * @param pHead2
     * @return
     */
    public static ListNode findFirstCommonNodeByMap(ListNode pHead1, ListNode pHead2) {
        //warning 忘记判空了
        if (pHead1 == null || pHead2 == null) {
            return null;
        }
        Map<ListNode, Integer> map = new HashMap<>();
        ListNode p1 = pHead1;
        ListNode p2 = pHead2;
        //将第一个链表的节点依次放入map
        while (p1 != null) {
            map.put(p1, null);
            p1 = p1.next;
        }

        while (p2 != null) {
            if (map.containsKey(p2)) {
                return p2;
            }
            p2 = p2.next;
        }
        return null;
    }

    /**
     * 方法2:通过集合来辅助查找
     *
     * @param headA
     * @param headB
     * @return
     */
    public static ListNode findFirstCommonNodeBySet(ListNode headA, ListNode headB) {
        ListNode h1 = headA;
        ListNode h2 = headB;

        List<ListNode> list = new ArrayList<>();
        while (h1 != null) {
            list.add(h1);
            h1 = h1.next;
        }

        while (h2 != null) {
            if (list.contains(h2)) {
                return h2;
            }
            h2 = h2.next;
        }
        return null;
    }

    /**
     * 方法3:通过栈
     */
    public static ListNode findFirstCommonNodeByStack(ListNode headA, ListNode headB) {
        Stack<ListNode> h1 = new Stack<>();
        Stack<ListNode> h2 = new Stack<>();

        ListNode ha = headA;//因为Java是值传递,所以不会改变外部头节点的取值,这2行代码去掉也无妨
        ListNode hb = headB;

        while (ha != null) {
            h1.push(ha);
            ha = ha.next;
        }
        while (hb != null) {
            h2.push(hb);
            hb = hb.next;
        }

//        while (!h1.empty()) {
//            ListNode temp1 = h1.pop();
//            ListNode temp2 = h2.pop();
//            if (temp1 == temp2) {
//                ListNode temp3 = h1.pop();
//                ListNode temp4 = h2.pop();
//                if (temp3 != temp4) {
//                    return temp1;
//                }
//            }
//        }

        //优化
        //1、用一个变量保存上一个节点
        //2、增加判断2个栈大小的逻辑,过滤空栈
//        ListNode pre = null;
//        while(h1.size() > 0 && h2.size() > 0){
//            if(h1.peek() == h2.peek()){
                pre = h1.peek();
                h1.pop();
                h2.pop();
//                //优化
//                pre = h1.pop();
//                h2.pop();
//                if(h1.peek() != h2.peek()){
//                    return pre;
//                }
//            }else {
//                return null;
//            }
//        }
//        return null;

        //还能优化
        ListNode pre = null;
        while (h1.size() > 0 && h2.size() > 0) {
            if (h1.peek() == h2.peek()) {
                //优化
                pre = h1.pop();
                h2.pop();
            } else {
                break;
            }
        }
        return pre;
    }

    /**
     * 方法4:通过序列拼接(空间复杂度为O(1))
     */
    public static ListNode findFirstCommonNodeByCombine(ListNode pHead1, ListNode pHead2) {
        //双指针法
        //算法思想:如果有A、B,2个链表,拼接成AB,BA,如果AB有公共节点,那肯定是唯一公共节点(由单链表定义可知)
        //所以一直遍历到相同的公共节点,就是要找的第一个公共节点

        //判空
        if (pHead1 == null && pHead2 == null) {
            return null;
        }

        ListNode p1 = pHead1;
        ListNode p2 = pHead2;

        while (p1 != p2) {
            //这里的p1 != p2不能替换成true,否则遇到没有公共节点的情况会死循环
            p1 = p1.next;
            p2 = p2.next;
            if (p1 != p2) {
                if (p1 == null) {
                    p1 = pHead2;
                }

                if (p2 == null) {
                    p2 = pHead1;
                }
            }
        }
        return p1;
    }

    /**
     * 方法5:通过差值来实现(空间复杂度为O(1))
     *
     * @param pHead1
     * @param pHead2
     * @return
     */
    public static ListNode findFirstCommonNodeBySub(ListNode pHead1, ListNode pHead2) {
        //虽然代码看起来比之前的几种方法多了不少,但思路也比较简单,很巧妙
        //算法思想:首先遍历一次,获取2个链表的长度,然后再次遍历,第二次遍历,先让长的链表先走长度差值的距离
        //然后2个链表再一起往下走,最后相等的第一个节点,就是要找的节点
        //有一种对齐的思想在里面

        //判空
        if (pHead1 == null && pHead2 == null) {
            return null;
        }

        ListNode p1 = pHead1;
        ListNode p2 = pHead2;

        int lengthP1 = 0;
        int lengthP2 = 0;

        //分别获取2个链表的长度
        while (p1 != null) {
            p1 = p1.next;
            lengthP1++;
        }

        while (p2 != null) {
            p2 = p2.next;
            lengthP2++;
        }

        //计算差值,确保结果为绝对值
        int sub = lengthP1 > lengthP2 ? lengthP1 - lengthP2 : lengthP2 - lengthP1;

        p1 = pHead1;
        p2 = pHead2;
        //再次进行遍历

        if (lengthP1 > lengthP2) {
            while (sub > 0) {
                p1 = p1.next;
                sub--;
            }
        } else {
            while (sub > 0) {
                p2 = p2.next;
                sub--;
            }
        }

        while (p1 != p2) {
            p1 = p1.next;
            p2 = p2.next;
        }
        return p1;
    }

    /**
     * 简单构造两个链表
     *
     * @return
     */
    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;
    }

    static class ListNode {
        public int val;
        public ListNode next;

        ListNode(int x) {
            val = x;
            next = null;
        }
    }

    public static String toString(ListNode head) {
        ListNode current = head;
        StringBuilder sb = new StringBuilder();
        while (current != null) {
            sb.append(current.val).append("\t");
            current = current.next;
        }
        return sb.toString();
    }
}
  • 9
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值