算法通关村第一关--链表经典问题笔记

1、找出两个链表的第一个公共节点

输入两个链表(例如下面两个链表),两个链表的头节点都是已知的,相交之后成为一个单链表,但是相交的位置未知,并且相交之前的节点数也是未知的,请设计算法找到两个链表的合并点。
在这里插入图片描述
解决问题的一个屡试不爽的方法:把常用的数据结构(数组、链表、队、栈、哈希、集合、树、堆)和常用的算法思想(查找、排序、双指针、递归、迭代、分治、贪心、回溯和动态规划等等)想一遍,看看哪些能解决。
首先最简单的是蛮力法,将第一个链表中的每一个节点依次和第二个链表的节点进行对比,当出现相同的节点指针时即为相交点,不过此方法时间复杂度高,不宜使用。

1.1 使用哈希或集合

先将一个链表元素全部存到Map(Set)中,然后一边遍历第二个链表,一边检测Hash中是否存在当前节点,如果有交点那么一定能检测出来,代码如下:

    /**
     * 通过hash辅助查找
     *
     * @param head1
     * @param head2
     * @return
     */
    public static ListNode seekFirstCommonNodeByHash(ListNode head1, ListNode head2) {
        if (head1 == null || head2 == null) {
            return null;
        }
        ListNode currNode1 = head1;
        ListNode currNode2 = head2;
        //将第一个链表元素全部存到map中
        Map<ListNode, Integer> hashMap = new HashMap<>();
        while (currNode1.next != null) {
            hashMap.put(currNode1, null);
            currNode1 = currNode1.next;
        }
        //遍历第二个链表
        while (currNode2.next != null) {
            if (hashMap.containsKey(currNode2)) {
                return currNode2;
            }
            currNode2 = currNode2.next;
        }
        return null;
    }

	/**
     * 通过集合Set辅助查找
     *
     * @param head1
     * @param head2
     * @return
     */
    public static ListNode seekFirstCommonNodeBySet(ListNode head1, ListNode head2) {
        if (head1 == null || head2 == null) {
            return null;
        }
        Set<ListNode> set = new HashSet<>();
        //将第一个链表元素全部存到map中
        while (head1.next != null) {
            set.add(head1);
            head1 = head1.next;
        }
        //遍历第二个链表
        while (head2.next != null) {
            if (set.contains(head2)) {
                return head2;
            } else {
                head2 = head2.next;
            }
        }
        return null;
    }

	/**
     * 创建两个链表
     *
     * @return
     */
    public static ListNode[] createLinkedList() {
        ListNode[] heads = new ListNode[2];
        //构建第一个链表,1 2 3 4 5 6
        heads[0] = new ListNode(1);
        ListNode currNode1 = heads[0];
        currNode1.next = new ListNode(2);
        currNode1 = currNode1.next;
        currNode1.next = new ListNode(3);
        currNode1 = currNode1.next;

        //构建第二个链表 8 9 4 5 6
        heads[1] = new ListNode(8);
        ListNode currNode2 = heads[1];
        currNode2.next = new ListNode(9);
        currNode2 = currNode2.next;

        //构建公共交点以及后面的节点
        ListNode commonNode;
        commonNode = new ListNode(4);
        currNode1.next = commonNode;
        currNode2.next = commonNode;

        commonNode.next = new ListNode(5);
        commonNode = commonNode.next;
        commonNode.next = new ListNode(6);

        return heads;
    }

1.2 使用栈

需要使用两个栈,分别将两个链表的节点入两个栈,然后分别出栈(后进先出),如果相等就继续出栈,一直找到最晚出栈的相等的那一组,这种方法需要两个O(n)的空间,代码如下:

	/**
     * 通过栈Stack辅助查找
     *
     * @param head1
     * @param head2
     * @return
     */
    private static ListNode seekFirstCommonNodeByStack(ListNode head1, ListNode head2) {
        Stack<ListNode> stackA = new Stack<>();
        Stack<ListNode> stackB = new Stack<>();
        //分别将两个链表的节点入两个栈
        while (head1.next != null) {
            stackA.push(head1);
            head1 = head1.next;
        }
        while (head2.next != null) {
            stackB.push(head2);
            head2 = head2.next;
        }
        //分别出栈,一直找到最晚出栈的相等的那一组
        ListNode listNode = null;
        while (stackA.size() > 0 && stackB.size() > 0) {
            if (stackA.peek() == stackB.peek()) {
                listNode = stackA.pop();
                stackB.pop();
            } else {
                break;
            }
        }
        return listNode;
    }

1.3 拼接两个链表

将A链表与B链表互相拼接即变成AB与BA链表,如下
A:1 -> 2 -> 3 -> 4 -> 5
B:9 -> 7 -> 4 -> 5
分别拼接成AB与BA后:
AB:1 -> 2 -> 3 -> 4 -> 5 -> 9 -> 7 -> 4 -> 5
BA:9 -> 7 -> 4 -> 5 -> 1 -> 2 -> 3 -> 4 -> 5

	/**
     * 通过序列拼接辅助查找
     *
     * @param head1
     * @param head2
     * @return
     */
    public static ListNode seekFirstCommonNodeByMerge(ListNode head1, ListNode head2) {
        if (head1 == null || head2 == null) {
            return null;
        }
        ListNode node1 = head1;
        ListNode node2 = head2;
        while (node1 != node2) {
            node1 = node1.next;
            node2 = node2.next;
            //读到相等为止
            if (node1 != node2) {
                //当A遍历完直接将B接到A上,最终成为AB
                if (node1 == null) {
                    node1 = head2;
                }
                //当B遍历完直接将A接到B上,最终成为BA
                if (node2 == null) {
                    node2 = head1;
                }
            }
        }
        return node1;
    }

1.4 差和双指针

分别获取到两个链表的长度 |S1-S2| ,让长的先走 |S1-S2|,然后两个链表同时向前走,节点一样的时候就找到公共节点了,代码如下

	/**
     * 通过差值助查找
     *
     * @param head1
     * @param head2
     * @return
     */
    public static ListNode seekFirstCommonNodeBySub(ListNode head1, ListNode head2) {
        if (head1 == null || head2 == null) {
            return null;
        }
        ListNode node1 = head1;
        ListNode node2 = head2;
        //分别获取到两个链表的长度
        int length1 = 0;
        while (node1.next != null) {
            node1 = node1.next;
            length1++;
        }
        int length2 = 0;
        while (node2.next != null) {
            node2 = node2.next;
            length2++;
        }
        int sub = length1 - length2 > 0 ? length1 - length2 : length2 - length1;
        //让长的先走
        node1 = head1;
        node2 = head2;
        if (length1 > length2) {
            int a = 0;
            while (sub > a) {
                node1 = node1.next;
                a++;
            }
        }
        if (length2 > length1) {
            int a = 0;
            while (sub > a) {
                node2 = node2.next;
                a++;
            }
        }
        //两个链表同时向前走,节点一样的时候就找到公共节点了
        while (node1 != node2) {
            node1 = node1.next;
            node2 = node2.next;
        }
        return node1;
    }

2、判断链表是否为回文序列

如输入: 1 -> 2 -> 2 ->1
用O(n)时间复杂度和O(1)空间复杂度解决。
最简单粗暴的方法是将链表元素都赋值到数组中,然后从数组两端向中间对比,但这种方法会被视为逃避链表。

2.1 使用栈判断

将链表元素全部压栈,然后一边出栈一边重新遍历链表(此时相当于是链表头和链表尾部同时向中间进行对比),只要有一个不相等,就不是回文序列,代码如下:

	/**
     * 将链表元素全部压栈,然后一边出栈一边重新遍历链表,只要有一个不相等,就不是回文序列
     *
     * @param head
     * @return
     */
    public static boolean ifPalindromicByStack(ListNode head) {
        if (head == null) return true;
        ListNode curr = head;
        //把链表节点的值存放到栈中
        Stack<Integer> stack = new Stack();
        int length = 0;
        while (curr != null) {
            stack.push(curr.val);
            curr = curr.next;
            length++;
        }
        //然后再出栈(后进先出) 只比较一半的元素(只出栈一半的元素、链表只遍历一半)
        length >>= 1;
        while (length-- >= 0) {
            if (head.val != stack.pop()) {
                return false;
            }
            head = head.next;
        }
        return true;
    }

2.2 使用递归判断

类似于先将一个链表反转再与原始链表对比

    static ListNode temp;
	/**
     * 通过递归的方法判断
     *
     * @param head
     * @return
     */
    public static boolean ifPalindromicByRecursion(ListNode head) {
        temp = head;
        return checkNode(temp);
    }

    public static boolean checkNode(ListNode head) {
        if (head == null) {
            return true;
        }
        //有一个为false,后面所有的都是返回false
        Boolean result = checkNode(head.next) && (head.val == temp.val);
        temp = temp.next;
        return result;
    }

2.3 使用双指针判断

通过双指针思想里面的快慢指针来判断,fast一次走两步,slow一次走一步,当fast到达链表尾部的时候,slow刚好走到一半的位置,然后从头开始逆序一半的元素或者从slow开始逆序一半的元素,代码如下:

	/**
     * 使用快慢指针判断
     *
     * @param head
     * @return
     */
    private static boolean ifPalindromicByTwoPointers(ListNode head) {
        //安全性校验
        if (head == null && head.next == null) {
            return false;
        }
        ListNode slow = head, fast = head;
        ListNode pre = head, prePre = null;
        while (fast != null && fast.next != null) {
            pre = slow;
            slow = slow.next;
            fast = fast.next.next;
            //获取到slow已经遍历过的逆序的链表
            pre.next = prePre;
            prePre = pre;
        }
        //当序列是单数个数时,此时slow里面元素比pre多一个
        if (fast != null) {
            slow = slow.next;
        }
        //比较slow与pre
        while (pre != null && slow != null) {
            if (pre.val != slow.val) {
                return false;
            }
            pre = pre.next;
            slow = slow.next;
        }
        return true;
    }
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值