算法通关村第一关——链表经典问题之两个链表第一个公共子结点与回文链表笔记

两个链表第一个公共子结点

题目:输入两个链表,找出它们的第一个公共结点

image.png

首先要明确题目,第一个公共结点之后链表都是相同的。由此,我们假设有两个链表a,b并且他们有公共子结点,那么只要在其中一个链表中顺序遍历找到另一个链表中的相同结点,则为第一个公共子结点。

方法一:利用HashMap进行寻找

先遍历一个链表,将其结点存储在HashMap中,再遍历另一个链表并判断每一个结点是否存在于map中,第一个找到的存在结点即为第一个公共子结点。

/**
     * 通过HashMap寻找
     *
     * @return
     */
    public static ListNode findByHash(ListNode head1, ListNode head2) {
        if (head1 == null || head2 == null) {
            return null;
        }
        ListNode curr1 = head1;
        HashMap<ListNode, Integer> hashMap = new HashMap<>();
        while (curr1 != null) {
            hashMap.put(curr1, null);
            curr1 = curr1.next;
        }
        ListNode curr2 = head2;
        while (curr2 != null) {
            if (hashMap.containsKey(curr2)) {
                return curr2;
            }
            curr2 = curr2.next;
        }
        return null;
    }

方法二:利用集合去寻找

整体思路和HashMap一致,因为map是键值对,而这里面只需要能存储一列数据的容器即可。因此使用集合会更加合适。

/**
     * 通过集合寻找
     *
     * @return
     */
    public static ListNode findBySet(ListNode head1, ListNode head2) {
        if (head1 == null || head2 == null) {
            return null;
        }
        HashSet<ListNode> set = new HashSet<>();
        ListNode curr1 = head1;
        ListNode curr2 = head2;
        while (curr1 != null) {
            set.add(curr1);
            curr1 = curr1.next;
        }
        while (curr2 != null) {
            if (set.contains(curr2)) {
                return curr2;
            }
            curr2 = curr2.next;
        }
        return null;
    }

方法三:利用双指针进行寻找

此方法的主要思路是利用第一个公共子结点之后的内容与长度一致,因此我们将两个链表的遍历指针调整至相同长度,再此基础上进行遍历,则第一个一致的结点即为我们要找的子结点。具体做法是先遍历出两个链表的长度,然后将长的一个链表多出来的部分排出比较范围,当剩余长度相同时,进行遍历比较

/**
     * 利用双指针进行查找
     *
     * @param head1
     * @param head2
     * @return
     */
    public static ListNode findFirstCommonNode(ListNode head1, ListNode head2) {
        if (head1 == null || head2 == null) {
            return null;
        }
        ListNode curr1 = head1;
        ListNode curr2 = head2;
        int L1 = 0;
        int L2 = 0;
        while (curr1 != null) {
            curr1 = curr1.next;
            L1++;
        }
        while (curr2 != null) {
            curr2 = curr2.next;
            L2++;
        }
        curr1 = head1;
        curr2 = head2;
        int distance = L1 - L2 > 0 ? L1 - L2 : L2 - L1;
        if (L1 > L2) {
            int a = 0;
            while (a < distance) {
                curr1 = curr1.next;
                a++;
            }
        }
        if (L2 > L1) {
            int b = 0;
            while (b < distance) {
                curr2 = curr2.next;
                b++;
            }
        }
        while (curr1 != curr2) {
            curr1 = curr1.next;
            curr2 = curr2.next;
        }
        return curr1;
    }

回文链表

题目:判断一个链表是否为回文链表

示例:

输入:1 -> 2 -> 2-> 1

输出:true

这里先记述一个比较简单的方法

方法一:利用栈——全部压栈

将链表全部压入栈内,然后一边出栈一边遍历链表,将每一对结点进行比较,只要有一个不同,则不为回文链表,反之,则为回文链表

/**
     * 全部压栈
     *
     * @return
     */
    public static boolean isPalindromicByStack(ListNode head) {
        if (head == null) {
            return false;
        }
        ListNode curr = head;
        Stack<ListNode> stack = new Stack<>();
        while (curr != null) {
            stack.push(curr);
            curr = curr.next;
        }
        curr = head;
        while (curr != null) {
            ListNode stackNode = stack.pop();
            // 要注意,这里必须比较的是两个结点的值,而不是结点本身
            if (curr.val != stackNode.val) {
                return false;
            }
            curr = curr.next;
        }
        return true;
    }

这里要注意一点,进行结点比较的时候就不能像上一个题目比较的时候一样直接对结点进行比较了,因为结点直接比较比较的是地址,上一个问题由于是指向同一地址因此可以判断。这里由于结点是倒序并且本身进栈后的结点地址也不再相同,因此只能也只需比较结点的val即可。这里由于实现算法的时候没有注意,稍微踩了一下坑。

合并有序链表

题目:将两个升序链表合并为一个新的升序链表并返回,新链表是通过拼接给定的两个链表的所有结点组成的。

因为是两个升序链表,因此基本思路为,定义一个新的链表,然后同时对两个链表进行遍历,将小的一个结点放进新的链表当中,当遍历结束,新的链表也就成了我们要求的合并后的有序链表。

方法一:利用新链表去合并

/**
     * 方法一
     *
     * @param head1
     * @param head2
     * @return
     */
    public static ListNode mergeTwo(ListNode head1, ListNode head2) {
        ListNode newHead = new ListNode(-1);
        ListNode result = newHead;
        while (head1 != null || head2 != null) {
            // 两者都不为空
            if (head1 != null && head2 != null) {
                if (head1.val < head2.val) {
                    newHead.next = head1;
                    head1 = head1.next;
                    newHead = newHead.next;
                } else if (head1.val > head2.val) {
                    newHead.next = head2;
                    head2 = head2.next;
                    newHead = newHead.next;
                } else {
                    newHead.next = head1;
                    head1 = head1.next;
                    newHead.next.next = head2;
                    head2 = head2.next;
                    newHead = newHead.next.next;
                }
            } else if (head1 == null && head2 != null) {
                newHead.next = head2;
                head2 = head2.next;
                newHead = newHead.next;
            } else if (head1 != null && head2 == null) {
                newHead.next = head1;
                head1 = head1.next;
                newHead = newHead.next;
            }
        }
        return result.next;
    }

在这个方法实现的时候踩了一个坑,就是else后面的代码。要在插入新链表之后立刻对指针进行移动。即不能写成下述顺序:因为链表是指向内存地址,而不是真的创建一个新的结点添加到新链表当中。因此如果不及时更新指针,而是先执行newHead.next.next = head2。会导致原本的head1指向head2。

newHead.next = head1;
newHead.next.next = head2;
head1 = head1.next;
head2 = head2.next;
newHead = newHead.next.next;

方法二:针对方法一的优化

可以看到方法一,要针对很多的情况进行判断,十分不美观以及臃肿。其实可以将其分为两种情况。一种是全不为空,剩下就是任意一个为空,而第二种情况可以用一个三元运算符直接操作,不需要进行while循环。因为当只有一个链表的时候,直接将其接到新链表最后即可。

/**
     * 方法一改进版
     *
     * @param head1
     * @param head2
     * @return
     */
    public static ListNode mergeTwoPlus(ListNode head1, ListNode head2) {
        ListNode newHead = new ListNode(-1);
        ListNode curr = newHead;
        while (head1 != null && head2 != null) {
            if (head1.val <= head2.val) {
                newHead.next = head1;
                head1 = head1.next;
                newHead = newHead.next;
            } else {
                newHead.next = head2;
                head2 = head2.next;
                newHead = newHead.next;
            }
        }
        newHead.next = head1 != null ? head1 : head2;
        return curr.next;
    }

    private static ListNode initLinkedList(int[] array) {
        ListNode head = null, cur = null;

        for (int i = 0; i < array.length; i++) {
            ListNode newNode = new ListNode(array[i]);
            newNode.next = null;
            if (i == 0) {
                head = newNode;
                cur = head;
            } else {
                cur.next = newNode;
                cur = newNode;
            }
        }
        return head;
    }

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值