数据结构与算法学习笔记 Chapter1 链表(2)高频面试题

96 阅读4分钟

链表高频面试算法题

剑指offer52

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

输入两个链表,找出它们的一个公共节点。两个链表的头节点是已知的,相交之后成为一个单链表,但是相交的位置未知,并且相交之前的节点数也是未知的,请设计算法找到两个链表的合并点。

思路:

  1. 对于单链表而言,每一个节点都会存放下一个节点的地址,重点在于只存放一个节点。**所以说明每一个节点只会有一个后继,但每一个节点可能有多个前驱。**可以得出结论,该表的结构应该是类似图1而非图2。
  2. 当知道链表的头节点时候,就可以遍历所有节点,这道题目两个链表的头节点已知,那么我们可以遍历两个链表,取出两个链表所有的值和地址,通过比较判定则可得出合并点。

解题:

  1. 哈希表和集合:先后遍历两个链表,然后将两个链表的值存到set中。在遍历第二个同时,检测第一个链表是否存在当前的节点,如果有交点即可检测出来。
package lukasy.chapter1_linklist.level2.topic1第一个公共节点;

import lukasy.chapter1_linklist.level1.ListNode;

import java.util.HashMap;
import java.util.HashSet;
import java.util.Set;
/**
 *
 * @author LukAsy_
 * @date 2023/11/28
 */
public class FindFirstCommonNode {
    /**
     * method1:通过Hash辅助查找
     *
     * @param pHead1
     * @param pHead2
     * @return
     */
    public static ListNode findFirstCommonNodeByMap(ListNode pHead1, ListNode pHead2) {
        if (pHead1 == null || pHead2 == null) {
            return null;
        }
        ListNode cur1 = pHead1;
        ListNode cur2 = pHead2;

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

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


    /**
     * 方法2:通过集合来辅助查找
     *
     * @param headA
     * @param headB
     * @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;
    }
}

LeetCode234

判断链表是否为回文序列题

示例 1:

输入:head = [1,2,2,1]

输出:true

示例 2:

输入:head = [1,2]

输出:false

思路:

  1. 首先明确一个概念,怎么样的数据才是回文序列。回文序列指的是正读和反读都相同的序列。换句话说,如果一个序列从左到右读和从右到左读是一样的,那么它就是回文的。
  2. 我们要要判断一个链表是否是回文序列,其实就是比较前半段和后半段的内容是否一样,那使用栈的先进后出的特性可以得到反向排序的链表,将其与正向排序的链表做比较是否每个值都一样就可以判断出来了。
  3. 综上思路就是,先遍历一遍链表,然后将链表压入栈中,再出栈,出栈同时再遍历链表并比较出栈和遍历的元素是否相同。

public class IsPalindromic {
    public static boolean isPalindromeByAllStack(ListNode head) {
        ListNode cur = head;
        Stack<Integer> stack = new Stack<>();
        while (cur != null) {
            stack.push(cur.val);
            cur = cur.next;
        }
        while (head != null) {
            if (head.val != stack.pop()) {
                return false;
            }
            head = head.next;
        }
        return true;
    }
}

LeetCode21

合并两个有序链表

将两个链表按顺序合并。 思路:

  1. 新建一个链表,将两个链表分别遍历,对比两个链表每个值,较小的则连入新链表,重复这个过程知道两个链表都遍历完为止。
package lukasy.chapter1_linklist.level2.topic3合并有序链表;

import lukasy.chapter1_linklist.level1.ListNode;

/**
 * @author LukAsy_
 * @date 2023/12/2
 */
public class MergeList {
    /**
     * @param l1
     * @param l2
     * @return
     */
    public static ListNode mergeTwoListsMoreSimple(ListNode l1, ListNode l2) {
        ListNode prehead = new ListNode(-1);
        ListNode cur = prehead;
        while (l1 != null && l2 != null) {
            if (l1.val <= l2.val) {
                cur.next = l1;
                l1 = l1.next;
            } else {
                cur.next = l2;
                l2 = l2.next;
            }
            cur = cur.next;
        }
        cur.next = l1 == null ? l2 : l1;
        return cur.next;
    }

}

拓展合并链表

合并K个链表

思路: 有个合并两个链表的思路,那合并多个链表无非就是先将前两个链表进行对比合并,然后再将后面的链表和合并后的链表对比合并。

/**
     * 合并K个链表
     *
     * @param lists
     * @return
     */
    public static ListNode mergeKLists(ListNode[] lists) {
        ListNode cur = null;
        for (ListNode list : lists) {
            cur = mergeTwoListsMoreSimple(cur, list);
        }
        return cur;
    }

LeetCode1669

给你两个链表 list1 和 list2 ,它们包含的元素分别为 n 个和 m 个。 请你将 list1 中下标从 a 到 b 的全部节点都删除,并将list2 接在被删除节点的位置。 思路:

  1. 找出list1中需要删除部分的头节点的前一个节点和尾节点a-1、b。
  2. 找出删除部分头节点的前一个节点(a-1)是因为我们需要将该节点和list2的头节点相连接。
  3. 将list2的尾节点和list1后面的部分相连接。
/**
     * @param list1
     * @param list2
     * @param a     待删除链表位置的头
     * @param b     待删除链表位置的尾
     * @return
     */
    public ListNode mergeInBetween(ListNode list1, int a, int b, ListNode list2) {
        ListNode pre1 = list1, post1 = list1, post2 = list2;
        int i = 0, j = 0;
        //遍历l1并找出需要删除部分的头尾
        while (pre1 != null && post1 != null && j < b) {
            if (i != a - 1) {
                pre1 = pre1.next;
                i++;
            }
            if (j != b) {
                post1 = post1.next;
                j++;
            }
        }
        //遍历l2
        while (list2.next != null) {
            post2 = post2.next;
        }

        //将l2的尾部接到l1需要删除部分头节点的前一个节点,l2头接l1后半部分的头
        pre1.next = list2;
        post2.next = post1.next;
        return list1;
    }