编程导航算法通关村第 1 关 | 白银挑战——链表经典问题

本篇为链表部分的白银挑战难度,会学习到链表相关的一些经典问题的解决办法。

在开始正式内容之前,补充上篇没有发现的一个细节。今天翻群聊天记录的时候,发现一个网名为“真椛难寻”同学发布的公众号,发现他写的博客带有很多自己的理解,比我只是根据学习内容重新转述一遍要强不少。其中我发现了一个关于链表的细节,就是每次涉及到遍历链表的时候,都需要用一个 ListNode  类的对象 current 引用来获取头节点,切记不能直接操作头节点 head ,否则一旦找不到头节点,我们的链表就被破坏了。

一、寻找两个链表的第一个公共子节点

思路:将常用的数据结构和算法思想都想一遍,最终得出一个或者若干个解题思路,并得出最优解。

1.1 哈希

将其中一个链表存入HashMapListNode的节点对象作为HashMap的key值存储,再遍历另一个链表,使用 hashMap.containsKey()方法检查HashMap中是否存在指定的键,遍历到第一个存在相同的键值,即跳出遍历,return该节点。

public static ListNode findFirstCommonNodeByMap(ListNode pHead1, ListNode pHead2) {
        if (pHead1 == null || pHead2 == null) {
            return null;
        }
        ListNode current1 = pHead1;
        ListNode current2 = pHead2;

        HashMap<ListNode, Integer> hashMap = new HashMap<ListNode, Integer>();
        while (current1 != null) {
            hashMap.put(current1, null);
            current1 = current1.next;
        }

        while (current2 != null) {
            if (hashMap.containsKey(current2)) {
                return current2;
            }
            current2 = current2.next;
        }

        return null;
    }

1.2 Set集合

思路与哈希差不多,使用set.add()方法将第一个链表的每一个节点放入,再遍历第二个链表,使用set.contains()方法,检查set集合中是否包含第二个链表的节点,最后return这个节点。

public static ListNode findFirstCommonNodeBySet(ListNode headA, ListNode headB) {
        Set<ListNode> 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.3 栈

栈的特点是先进后出,可以把他想象成放羽毛球的圆筒,最先放进去的只能最后拿出来。因此,我们将两个链表的每一个节点都分别放入两个栈对象stackA和stackB中,使用stack.peek()方法比较两个栈对象最后放入的节点是否相同,如果相同,则使用stack.pop()方法将最后放入的节点删除,并返回栈顶节点(用一个对象存储下来),当两个栈的顶层节点不相等,结束循环,并返回之前最后一次获得的栈顶元素。

public static ListNode findFirstCommonNodeByStack(ListNode headA, ListNode headB) {
        Stack<ListNode> stackA = new Stack();
        Stack<ListNode> stackB = new Stack();
        while (headA != null) {
            stackA.push(headA);
            headA = headA.next;
        }
        while (headB != null) {
            stackB.push(headB);
            headB = headB.next;
        }

        ListNode preNode = null;
        while (stackB.size() > 0 && stackA.size() > 0) {
            if (stackA.peek() == stackB.peek()) {
                preNode = stackA.pop();
                stackB.pop();
            } else {
                break;
            }
        }
        return preNode;
    }

1.4 拼接字符串

假设有两个链表,链表A的节点为:1 - 2 - 3 - 4 - 5 - 6,链表B的节点为a - b - 4 - 5 - 6,用链表A拼链表B,得到的结果是1 - 2 - 3 - 4 - 5 - 6 - a - b - 4 - 5 - 6,用链表B后面拼上链表A,得到的结果是a - b - 4 - 5 - 6 - 1 - 2 - 3 - 4 - 5 - 6,分别遍历两个拼接以后的链表,就会发现最后几个节点都是一样的,即可得出相同的节点。

接下来就是对使用内存空间优化一下,不要创建两个新的链表,而是在一个链表遍历完以后,直接赋值为另外一个链表,即一个链表遍历完以后,接入另一个链表进行遍历。

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 != p2) {
                if (p1 == null) {
                    p1 = pHead2;
                }
                if (p2 == null) {
                    p2 = pHead1;
                }
            }
        }
        return p1;
    }

接下来我们解释一下,循环体里为什么需要加一个判断if (p1 != p2) 。简单来说,如果序列不存在交集的时候陷入死循环,例如 list1是1 2 3,list2是4 5 ,很明显,如果不加判断,list1和list2会不断循环,出不来。

1.5 差和双指针

通过观察,可以发现两个链表从功公共节点开始后面的节点都是一样的,也就是只要将前面的节点统一遍历后,就可以得到第一个公共节点。但是链表长度并不相同,所以我们第一步就是要获得两个链表的长度,这边称两个长度为l1和l2,然后计算两个链表长度的差值|L2-L1|,让长度更长的链表先遍历|L2-L1|位的长度,此时剩下的长度与短的链表长度一样了,就开始同时遍历两个链表,直到某个点,得到的节点一样,就可以得到结果了。

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) {
            current1 = current1.next;
            l1++;
        }

        while (current2 != null) {
            current2 = current2.next;
            l2++;
        }
        current1 = pHead1;
        current2 = pHead2;

        int sub = l1 > l2 ? l1 - l2 : l2 - l1;

        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;
    }

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值