备战Java后端【Day1】

这篇博客介绍了链表的基本概念和不同类型的链表,包括单链表、循环链表和双向链表。重点讲解了链表的创建、操作,如合并、查找、删除、反转等,并提供了具体的Java实现。此外,还探讨了在排序链表中删除重复节点、合并多个有序链表等高级操作。
摘要由CSDN通过智能技术生成

备战Java后端【Day1】

数据结构1-1

链表1


单链表的基本操作:

  • 合并两个有序链表
  • 合并k个有序链表
  • 寻找单链表的中点
  • 寻找单链表中的第k个节点
  • 判断单链表是否包含环并找出环起点
  • 判断两个单链表是否相交并找出交点

学习目标:

  • 了解链表基本知识和基本操作
  • 合并两个有序链表
  • 合并k个有序链表

学习内容:

什么是链表

  • 链表是一种根据元素节点逻辑关系排列起来的一种数据结构。利用链表可以保存多个数据,这一点类似于数组的概念,但是数组本身有一个缺点—— 数组的长度固定,不可改变,在长度固定的情况下首选的肯定是数组,但是在现实的开发之中往往要保存的内容长度是不确定的,那么此时就可以利用链表这样的结构来代替数组的使用。
  • 链表是一种物理存储单元上非连续、非顺序的存储结构,数据元素的逻辑顺序是通过链表中的指针链接次序实现的。链表由一系列结点(链表中每一个元素称为结点)组成,结点可以在运行时动态生成。每个结点包括两个部分:一个是存储数据元素的数据date域(储存的对象),另一个是存储下一个结点地址的指针next域(对下一个节点的引用)。
  • 链表的特点在于:寻址困难,插入和删除容易。链表动态地进行存储分配,可以适应数据动态地增减的情况,而且可以方便地插入、删除数据项目。
  • 链表存储区间离散,占用内存比较宽松,故空间复杂度很小,但查找的时间复杂度很大,达o(n)。

单向链表

  • 单链表是较为简单的链表,单链表的指针指向同一方向的下一个元素。在单链表中,有两个特殊的节点,头节点和尾节点。头节点(也叫根节点)是单链表中的第一个节点,是单链表中的关键节点。通过头节点,我们可以将指针指向头节点,遍历整个单链表,来实现增删改查等操作。这样操作可以防止链表为空(链表若为空,则头节点指针域为null),并且可以保持对单链表操作的统一性(从头节点开始遍历),简化操作减少bug。尾节点是单链表中的最后一个节点,它的指针指向一个空的地址null(没有下一个节点了)。在单链表的操作中,我们可以通过定义尾节点的指针来实现不同的方法。
    在这里插入图片描述

循环链表

  • 循环链表的结构与单链表类似,不同之处在于单链表的尾节点指向一个null空地址,而循环链表的尾节点指针指向了头节点。这样,就形成了一个首尾相连的环形结构的链表。循环链表适合用来处理环形结构的数据。
    在这里插入图片描述

双向链表

  • 双向链表是一种可以双向遍历的链表,它的指针可以指向前后两个方向的节点。双向链表拥有头节点和尾节点,可以从头节点进入链表进行操作。与单链表不同的是,双向链表拥有两个指针地址,多了一个指向前一节点的引用地址。双向链表的优点是,在操作链表中的上一节点时可以通过前指针直接向前遍历,而无需从头节点开始重新遍历,提高了链表的效率。其缺点是比单链表结构多了一个指针地址,会占用更多的内存来进行存储。
    在这里插入图片描述

双向循环链表

  • 双向循环链表是循环链表与双向链表的结合,每个节点拥有前后指针,并且尾节点指向头节点形成循环。可以用来灵活处理环形结构的数据。
    在这里插入图片描述

链表操作

  • 创建链表类及节点: 在单链表中,我们首先要定义链表中最基础的节点元素。如上图所示,节点中包括两个属性,指针next和数据data。同时,我们可以在创建时定义链表的头节点、尾节点和链表长度。

  • 链表的具体操作归纳如下,其中前半部分为一些基本操作,后半部分为一些例题。

import java.util.Comparator;

import java.util.Comparator;

/**
 * 该类将会参考JDK中LinkedList的实现,实现自己的!-单-!链表类-不使用哨兵节点。
 * 实现的功能主要是面试中比较常见的题目和链表的基本操作,没有实际生产用途,仅供学习参考。
 * <p>
 * 具有的功能如下所示:
 * <p>
 * <p>
 * 1.  判断链表是否为空
 * 2.  返回链表的长度
 * 3.  正向遍历链表
 * 4.  反向遍历链表
 * 5.  链表头部添加节点
 * 6.  链表尾部添加节点
 * 7.  指定值后面添加节点
 * 8.  所有指定值后面添加节点
 * 9.  指定值前面添加节点
 * 10. 所有指定值前面添加节点
 * 11. 返回链表的头
 * 12. 返回链表的尾
 * 13. 判断链表是否有环,如果有返回环的长度和入口点
 * 14. 删除指定值一个节点
 * 15. 删除指定值所有节点
 * 16. 在一个排序的链表中,删除重复的节点
 * 17. 在一个无序的链表中,删除重复的节点
 * 18. 查找节点是否存在
 * 19. 查找指定值节点个数
 * 20. 反转链表
 * 21. 返回链表中间节点-快慢指针
 * 22. 合并两个有序的单链表
 * 23. 合并K个有序的单链表
 * 24. 链表的选择排序
 * 25. 链表的冒泡排序
 * 26. 链表的归并排序
 * 27. 链表的快速排序
 * 28. 返回自身与另外一个链表的公共节点
 * 29. 返回倒数第k个节点
 * 30. 实现一个LRU缓存
 * 31. 判断链表是否是回文
 */
public class MyLinkedList<E extends Comparable> {
    // 链表的第一个元素
    Node<E> first;

    public MyLinkedList() {
    }

    /**
     * 1. 判断链表是否为空
     */
    public boolean isEmpty() {
        return isEmpty(first);
    }

    public static <E> boolean isEmpty(Node<E> head) {
        return head == null;
    }

    /**
     * 2. 返回链表的长度
     */
    public int size() {
        return size(first);
    }

    public static <E> int size(Node<E> head) {
        Node<E> temp = head;
        int size = 0;
        while (temp != null) {
            size++;
            temp = temp.next;
        }
        return size;
    }

    /**
     * 3. 正向遍历链表
     */
    public void printList() {
        Node<E> temp = first;
        while (temp != null) {
            System.out.println(temp.item);
            temp = temp.next;
        }
    }

    /**
     * 4. 反向遍历链表
     * 因为要反向遍历链表,考虑使用递归,因为递归调用栈既是一个天然的先入后出的队列
     */
    public void printListReverse() {
        Node<E> temp = first;
        printListReverse(temp);
    }

    private void printListReverse(Node<E> e) {
        if (e != null) {
            printListReverse(e.next);
            System.out.println(e.item);
        }
    }

    /**
     * 5. 链表头部添加节点
     *
     * @param e
     */
    public void addFirst(E e) {
        Node<E> temp = first;
        final Node<E> newNode = new Node<E>(e, null);
        // 如果头结点为空,新加结点就是头结点
        // 如果头结点不为空,则新加结点的next就是头结点,并且将头结点设置为新加结点
        if (temp == null) {
            first = newNode;
        }
        else {
            newNode.next = temp;
            first = newNode;
        }
    }

    /**
     * 6. 链表尾部添加节点
     * 直接找到最后一个节点,添加到最后一个节点的next
     * 特殊情况:如果链表长度为0,直接令first为新插入节点
     * 引入哨兵节点处理特殊情况,简化边界条件
     *
     * @param e <E> 要插入的值
     */
    public void addLast(E e) {
        Node<E> temp = first;
        Node<E> newNode = new Node<E>(e, null);
        if (temp == null) {
            first = newNode;
        }
        else {
            while (temp.next != null) {
                temp = temp.next;
            }
            temp.next = newNode;
        }
    }

    /**
     * 7. 指定值得后面添加节点
     * 如果链表中已经有了重复的值,以排在最前面的为准.
     * 如果插入成功,则返回true
     * 如果插入失败,则返回false
     * 特殊情况:
     * 1. 如果链表是空的,直接返回false
     *
     * @param aimValue <E>
     */
    public boolean addBefore(E aimValue, E insertValue) {
        Node<E> temp = first;
        if (temp == null) {
            return false;
        }
        else {
            while (temp != null) {
                if (temp.item.equals(insertValue)) {
                    Node<E> newNode = new Node<E>(aimValue, null);
                    newNode.next = temp.next;
                    temp.next = newNode;
                    return true;
                }
                else {
                    temp = temp.next;
                }
            }
            return false;
        }
    }

    /**
     * 8. 所有指定值后面添加节点
     * 链表中所有指定值的后面都添加节点
     * 返回插入值的个数
     * 逻辑:
     * 1. 如果头结点为空,直接返回0
     *
     * @param aimValue <E>
     */
    public int addBeforeAll(E aimValue, E insertValue) {
        // 插入值的个数
        int count = 0;
        if (first == null) {
            return 0;
        }
        else {
            Node<E> temp = first;
            while (temp != null) {
                if (temp.item.equals(aimValue)) {
                    Node<E> newNode = new Node<>(insertValue, null);
                    newNode.next = temp.next;
                    temp.next = newNode;
                    count++;
                    // 由于是在temp后面加了一个指定值,所以,要令temp = temp.next.next;
                    temp = temp.next.next;
                }
                else {
                    temp = temp.next;
                }
            }
        }
        return count;
    }

    /**
     * 9. 指定值前面添加节点
     *
     * @param aimValue    目标值
     * @param insertValue 要插入的值
     * @return 返回插入是否成功
     */
    public boolean addAfter(E aimValue, E insertValue) {
        // 如果头结点为空,直接返回false
        if (first == null) {
            return false;
        }
        else {
            // 特殊情况:
            // 如果链表只有一个头结点,且是目标值,直接插入到头结点前面,
            // 并重新设置头结点即可
            if (first.item.equals(aimValue)) {
                Node<E> newNode = new Node<>(insertValue, null);
                newNode.next = first;
                first = newNode;
                return true;
            }
            else {
                // 此处进入到else,表示链表不止仅有一个头结点
                Node<E> temp = first;
                // 可以发现这里判断不到头结点是否等于目标值,所以将之列为特殊情况
                while (temp.next != null) {
                    if (temp.next.item.equals(aimValue)) {
                        Node<E> newNode = new Node<>(insertValue, null);
                        newNode.next = temp.next;
                        temp.next = newNode;
                        return true;
                    }
                    else {
                        temp = temp.next;
                    }
                }
                return false;
            }
        }
    }

    /**
     * * 10. 所有指定值前面添加节点
     * 返回添加的次数
     *
     * @param aimValue    目标值
     * @param insertValue 要插入的值
     * @return 返回插入的个数
     */
    public int addAfterAll(E aimValue, E insertValue) {
        // 如果头结点为空,表示一定没有插入的位置,直接返回0。
        int count = 0;
        if (first == null) {
            return count;
        }
        else {
            // 特殊情况:
            // 如果链表只有一个头结点,且是目标值,直接插入到头结点前面,
            // 并重新设置头结点即可
            Node<E> temp = first;
            if (first.item.equals(aimValue)) {
                Node<E> newNode = new Node<>(insertValue, null);
                newNode.next = first;
                first = newNode;
                count++;
                temp = temp.next;
            }
            // 可以发现这里判断不到头结点是否等于目标值,所以将之列为特殊情况
            while (temp.next != null) {
                if (temp.next.item.equals(aimValue)) {
                    Node<E> newNode = new Node<>(insertValue, null);
                    newNode.next = temp.next;
                    temp.next = newNode;
                    temp = temp.next.next;
                    count++;
                }
                else {
                    temp = temp.next;
                }
            }
            return count;
        }
    }

    /**
     * 11. 返回链表的头结点
     *
     * @return
     */
    public Node<E> head() {
        return first;
    }

    /**
     * 12. 返回链表的尾
     *
     * @return
     */
    public Node<E> tail() {
        if (first == null) {
            return first;
        }
        else {
            Node<E> temp = first;
            while (temp.next != null) {
                temp = temp.next;
            }
            return temp;
        }
    }

    /**
     * 13. 判断链表是否有环,如果有,返回环的长度和入口点
     *
     * @return
     */
    public CircleResult getCircleResult() {
        return new CircleResult.Builder().build();
    }

    /**
     * 14. 删除指定值一个节点
     * 特殊情况:
     * 如果链表长度为0,或者aimValue为null,直接返回false,
     * 如果删除的是头结点,直接移动头结点即可
     * 如果删除的不是头结点
     *
     * @param
     */
    public boolean removeOneNode(E aimValue) {
        if (first == null || aimValue == null) {
            return false;
        }
        if (first.item.equals(aimValue)) {
            first = first.next;
            return true;
        }
        else {
            Node<E> temp = first;
            while (temp.next != null) {
                if (temp.next.item.equals(aimValue)) {
                    temp.next = temp.next.next;
                    return true;
                }
                temp = temp.next;
            }
        }
        return false;
    }

    /**
     * 15. 删除指定值所有节点,返回删除的个数
     *
     * @param
     */
    public int removeAllNode(E aimValue) {
        if (first == null || aimValue == null) {
            return 0;
        }
        // 循环删除头结点,直到头结点不为目标值
        int count = 0;
        while (first != null && first.item.equals(aimValue)) {
            first = first.next;
            count++;
        }
        if (first != null) {
            Node<E> temp = first;
            while (temp.next != null) {
                if (temp.next.item.equals(aimValue)) {
                    temp.next = temp.next.next;
                    count++;
                }
                else {
                    temp = temp.next;
                }
            }
        }
        return count;
    }

    /**
     * 16. 在一个排序的链表中,删除重复的节点
     * <p>
     * 思路一:暴力破解
     * 思路二:双指针法
     * <p>
     * 双指针法利用了排序的性质,由于链表已经排序,如果有重复的节点,那么重复的节点一定是排列在一起的。
     * 假设定义指针x为nodeX,指针y为nodeY。
     * 如果nodeX == nodeY,则另nodeY = nodeY.next;
     * 如果nodeX != nodeY, 则另nodeX.next = NodeY,删除nodeX->nodeY之间的节点,并另nodex = nodeX.next
     * nodeY = nodeY.next
     * Ruguo
     */
    public void removeOrderListDupNode() {
        // 首先确保头结点和aimValue不是null
        if (first == null) {
            throw new IllegalArgumentException("目标值不可为null");
        }
        Node<E> nodeX = first;
        Node<E> nodeY = first.next;
        while (nodeY != null) {
            if (nodeX.item.equals(nodeY.item)) {
                nodeY = nodeY.next;
                // 如果nodeY == null,则表示循环要结束了
                // 如果不另nodeX.next = null,可以会导致没有正确删除节点
                // 例如:如果链表是 1,2,2,2,2
                // 当nodeX = 2,nodeY = null时,循环直接跳出了,并没有删除节点。
                if (nodeY == null) {
                    nodeX.next = null;
                }
            }
            else {
                nodeX.next = nodeY;
                nodeX = nodeX.next;
                nodeY = nodeY.next;
            }
        }
    }

    /**
     * 17. 在一个无序的链表中,删除重复的节点
     * 思路一:对链表进行排序,调用removeOrderListDupNode
     * 思路二:暴力法,直接双重遍历,发现重复的删除后者
     *
     * @param aimValue
     */
    public void removeNotOrderListDupNode(E aimValue) {
    }

    /**
     * 18. 查找指定值节点是否存在
     * 思路:直接遍历一遍
     *
     * @param aimValue
     * @return
     */
    public boolean isExist(E aimValue) {
        Node<E> temp = first;
        while (temp != null) {
            if (temp.item.equals(aimValue)) {
                return true;
            }
            temp = temp.next;
        }
        return false;
    }

    /**
     * 19. 查找指定值节点的个数
     * 思路:直接遍历一遍
     *
     * @param aimValue
     * @return
     */
    public int countNode(E aimValue) {
        int count = 0;
        if (aimValue == null) {
            return 0;
        }
        Node<E> temp = first;
        while (temp != null) {
            if (temp.item.equals(aimValue)) {
                count++;
            }
            temp = temp.next;
        }
        return count;
    }

    /**
     * 翻转链表:非递归做法
     * 思路:
     * 非递归的做法可以简单的理解为创建一个新的头结点,依次遍历原链表,将原链表的数据依次插入到新链表的头结点前面。
     * 比如有一个链表为 1->2->3->2->5
     * 现在创建一个新的头结点,值为原链表的头结点的值
     * 1->NULL
     * 依次将2 3 2 5插入到新头结点的前面
     * 次序为
     * 2->1->null
     * 3->2->1->null
     * 2->3->2->1->null
     * 5->2->3->2->1->null
     * 我们可以对上述代码进行优化,
     * 其实我们并不需要创建的一个新的头结点,
     * 我们一次遍历链表,将后续的链表插入到自己的头结点前面即可,注意要
     * 更新链表的头结点。
     */
    public void reverse() {
        Node<E> temp = first;
        while (temp != null && temp.next != null) {
            Node<E> t = temp.next;
            temp.next = t.next;
            t.next = first;
            first = t;
        }
    }

    /**
     * 反转从位置m到n的链表。
     *
     * @param start
     * @param end
     */
    public void reverse(int start, int end) {
        // 1 <=m<=n<=链表长度

    }

    /**
     * 翻转链表:递归做法
     * 这种反转链表的递归做法很好理解,就是用递归替换了while循环。
     * 通过这种做法我们可以做一个总结。
     * while循环体 -> 递归
     * 循环体外的逻辑 -> 上层函数
     * 循环体的循环条件 -> 递归的终止条件
     * 循环体内的逻辑 -> 递归函数终止条件下面写 + 某尾递归调用自己。
     * 此处插入一个Python的写法,利用了Python的多元赋值
     * def reverse(head):
     * p,rev = head,None
     * while p:
     * rev,rev.next,p = p,rev,p.next
     * return rev
     */
    public void reverseRecur() {
        Node<E> temp = first;
        reverseRecur(temp);
    }

    private void reverseRecur(Node<E> temp) {
        if (temp == null || temp.next == null) {
            return;
        }
        Node<E> t = temp.next;
        temp.next = t.next;
        t.next = first;
        first = t;
        reverseRecur(temp);
    }

    /**
     * 非尾递归做法
     * 思路
     * https://blog.csdn.net/fx677588/article/details/72357389
     */
    public void reverseRecur2() {
        first = reverseRecur2(first);
    }

    private Node<E> reverseRecur2(Node<E> first) {
        if (first == null || first.next == null) {
            return first;
        }
        Node<E> newFirst = reverseRecur2(first.next);
        first.next.next = first;
        first.next = null;
        return newFirst;
    }

    /**
     * 返回链表中间节点-快慢指针
     * 使用快慢指针
     * 一开始起步相同,快指针每一次移动两个节点
     * 慢指针每次移动一个节点,当快指针到达末尾的时候,慢指针指向的位置就是中间节点。
     *
     * @return
     */
    public Node<E> middleNode() {
        return middleNode(first);
    }

    private Node<E> middleNode(Node<E> head) {
        if (head == null) {
            return null;
        }
        Node<E> slowNode = head;
        Node<E> fastNode = head.next;
        while (fastNode != null && fastNode.next != null) {
            slowNode = slowNode.next;
            fastNode = fastNode.next.next;
        }
        return slowNode;
    }

    /**
     * 合并两个有序的单链表
     * 插入合并一个有序的单链表
     * 注意,不允许自己合并自己
     *
     * @param
     */
    public static <T extends Comparable> MyLinkedList<T> mergeSortedList(MyLinkedList<T> thisList, MyLinkedList<T> otherList) {
        if (thisList == otherList) {
            throw new IllegalArgumentException("不允许自己与自己合并");
        }
        MyLinkedList<T> newMyLinkedList = new MyLinkedList<>();
        Node<T> thisNode = thisList.first;
        Node<T> otherNode = otherList.first;
        newMyLinkedList.first = mergeTwoNode(thisNode,otherNode);
        return newMyLinkedList;
    }

    public static <T extends Comparable> Node<T> mergeTwoNode(Node<T> thisNode, Node<T> otherNode) {
        if (thisNode == otherNode) {
            throw new IllegalArgumentException("不允许自己合并自己");
        }
        Node<T> resultNode = new Node<>(null, null);
        Node<T> resultTemp = resultNode;
        while (thisNode != null && otherNode != null) {
            if (thisNode.item.compareTo(otherNode.item) > 0){
                resultTemp.next = otherNode;
                otherNode = otherNode.next;
                resultTemp = resultTemp.next;
            } else {
                resultTemp.next = thisNode;
                thisNode = thisNode.next;
                resultTemp = resultTemp.next;
            }
        }
        if (thisNode!=null){
            resultTemp.next = thisNode;
        }
        if (otherNode != null){
            resultTemp.next = otherNode;
        }
        return resultNode.next;
    }

    /**
     * 合并K个有序的单链表
     * 仿照归并排序的思路所写
     *
     * @param
     */
    public static <T extends Comparable> MyLinkedList<T> mergeKSortedList(MyLinkedList<T>... myLinkedLists) {
        return mergeKSortedList(myLinkedLists, 0, myLinkedLists.length - 1);
    }

    private static <T extends Comparable> MyLinkedList<T> mergeKSortedList(MyLinkedList<T>[] myLinkedLists, int lo, int hi) {
        // 递归终止条件
        if (lo < hi) {
            int mid = lo + (hi - lo) / 2;
            MyLinkedList<T> leftList = mergeKSortedList(myLinkedLists, lo, mid);
            MyLinkedList<T> rightList = mergeKSortedList(myLinkedLists, mid + 1, hi);
            return mergeSortedList(leftList, rightList);
        }
        return myLinkedLists[lo];
    }

    /**
     * 链表的插入排序。 TODO ganju感觉有bug,谨慎参考,待修改
     * 思路:创建一个新的链表,遍历源链表,以此插入到新链表里面
     */
    public void insertSort(){
        if (first == null || first.next == null){
            // 如果链表长度为0或者1,则不需要排序
            return;
        }
        Node<E> head = first;

        Node<E> newFirst = new Node<>(null,null);
        Node<E> tempFirst = newFirst;
        while (head!=null){
            // head!=null 表示要将源链表遍历完
            // 找到插入点
            while( tempFirst != null && tempFirst.next != null && head.item.compareTo(tempFirst.next.item) > 0){
                tempFirst = tempFirst.next;
            }
            Node<E> temp = head;
            head = head.next;
            temp.next  = tempFirst.next;
            tempFirst.next = temp;
            tempFirst = newFirst;

        }
    }

    /**
     * 链表的选择排序
     * 算法思想:选择排序,从头至尾扫描序列,找出最小的一个元素,和第一个元素交换,接着从剩下的元素中继续这种选择和交换
     * 方式,最终得到一个有序序列。
     * @param
     */
    public void selectSort() {

        Node<E> sortedTail = first;

        while (sortedTail!=null){
            Node<E> minNode = findMinNode(sortedTail);
            swapTwoNodeValue(sortedTail,minNode);
            sortedTail = sortedTail.next;
        }
    }
    // 返回head节点后面值最小的节点
    private Node<E> findMinNode(Node<E> head){
        Node<E> minNode = head;
        head = head.next;
        while (head!=null){
            if (head.item.compareTo(minNode.item)<0){
                minNode = head;
            }
            head = head.next;
        }
        return minNode;
    }

    private void swapTwoNodeValue(Node<E> nodeOne,Node<E> nodeTwo){
        // 交换两个节点中的值
        E temp = nodeOne.item;
        nodeOne.item = nodeTwo.item;
        nodeTwo.item= temp;
    }

    /**
     * 链表的冒泡排序
     */
    public void bubbleSort() {
        if (first == null || first.next == null){
            return ;
        }
        // 冒泡排序一轮走完,会让最大的沉底
        // 所有已经有序的起始点,都在最后。
        // 已经有序节点的起始点
        Node<E> sortedStart = null;

        while (first!=sortedStart){
            Node<E> p = first;
            for (;p.next!=null && p.next!= sortedStart;p=p.next){
                if (p.item.compareTo(p.next.item)>0){
                    swapTwoNodeValue(p,p.next);
                }
            }
            sortedStart = p;
        }
    }

    /**
     * 链表的归并排序
     * 思路:
     * 利用快慢指针,找到链表的中间节点,之后递归合并
     */
    public void mergeSort() {
        first = mergeSort(first);
    }

    private Node<E> mergeSort(Node<E> head) {
        // 递归终止条件:链表长度为1
        if (head == null || head.next == null) {
            return head;
        }
        // 可以看到,归并排序的关键是找到中间节点,并拆封成两个链表,直到链表长度为1了,在递归的合并。
        Node<E> middleNode = middleNode(head);
        Node<E> leftNode = head;
        Node<E> rightNode = middleNode.next;
        middleNode.next = null;
        leftNode = mergeSort(leftNode);
        rightNode = mergeSort(rightNode);
        Node<E> newNode = mergeTwoNode(leftNode,rightNode);
        return newNode;
    }

    /**
     * 链表的快速排序
     * 只交换值
     */
    public void quickSortValue() {
        // 如果链表长度为0或者1,直接返回,不需要排序。
        if (first == null || first.next == null){
            return;
        }
        quickSortValue(first,null);
    }

    private void quickSortValue(Node<E> head,Node<E> tail){
        if (head != tail && head.next!=null){
            Node<E> mid = partition(head,tail);
            quickSortValue(head,mid);
            quickSortValue(mid.next,tail);
        }

    }
    private Node<E> partition(Node<E> lowNode,Node<E> hiNode){
        // 利用选择排序找到基准点
        E key = lowNode.item;
        Node<E> loc = lowNode;

        for (Node<E> i = lowNode.next;i!=hiNode;i=i.next){
            // 如果遇到了一个比基准点小的,都放在基准点左边
            if (i.item.compareTo(key) < 0){
                loc = loc.next;
                swapTwoNodeValue(loc,i);
            }
        }
        // 最后让基准点与loc交换数值,并返回loc
        swapTwoNodeValue(loc,lowNode);
        return loc;
    }


    /**
     * 链表的快速排序
     * 交换节点
     */
    public void quickSortNode(){
        first = quickSortNode(first);
    }
    private Node<E> quickSortNode(Node<E> head){
        if (head == null || head.next == null){
            return head;
        }
        Node<E> preHead = new Node<>(null,null);
        preHead.next = head;
        quickSortNode(preHead,head,null);
        return preHead.next;
    }
    private void quickSortNode(Node<E> preHead,Node<E> head,Node<E> tail){
        if (head!=tail && head.next!=tail){
            Node<E> middleNode = partition(preHead,head,tail);
            quickSortNode(preHead,preHead.next,middleNode);
            quickSortNode(middleNode,middleNode.next,tail);
        }
    }
    private Node<E> partition(Node<E> preHead,Node<E> head,Node<E> tail){
        E key = head.item;
        Node<E> littleNode = new Node<>(null,null);
        Node<E> largeNode  = new Node<>(null,null);
        Node<E> t_littleNode = littleNode;
        Node<E> t_largeNode = largeNode;

        for(Node<E> i = head.next;i!=tail;i=i.next){
            if (i.item.compareTo(key) < 0){
                t_littleNode.next = i;
                t_littleNode = i;
            } else {
                t_largeNode.next = i;
                t_largeNode = i;
            }
        }
        // preHead -> littleNode.next -> ...->t_littleNode->head->largeNode.next->...->t_largeNode->tail
        t_largeNode.next = tail;
        head.next = largeNode.next;
        t_littleNode.next = head;
        preHead.next = littleNode.next;
        return head;
    }

    /**
     * 返回自身与另外一个链表的公共节点
     *
     * @param otherLinkedList
     * @return
     */
    public Node<E> commonNode(MyLinkedList<E> otherLinkedList) {
        return null;
    }

    /**
     * 返回倒数第k个节点
     *
     * @return
     */
    public Node<E> reciprocalKNode() {
        return null;
    }

    public static class Node<E> {
        // 结点类
        E item;
        Node<E> next;

        Node(E element, Node<E> next) {
            this.item = element;
            this.next = next;
        }
    }
}

/**
 * 判断链表是否有段,如果有,返回环的长度和入口点
 * 结果类:
 * 是否有环
 * 环的长度
 * 入口点
 */
class CircleResult {
    boolean haveCircle; // 是否有环
    int circleLength;   // 环的长度
    MyLinkedList.Node entryNode; // 环的入口

    private CircleResult(Builder builder) {
        haveCircle = builder.haveCircle;
        circleLength = builder.circleLength;
        entryNode = builder.entryNode;
    }

    public static final class Builder {
        private boolean haveCircle;
        private int circleLength;
        private MyLinkedList.Node entryNode;

        public Builder() {
        }

        public Builder haveCircle(boolean val) {
            haveCircle = val;
            return this;
        }

        public Builder circleLength(int val) {
            circleLength = val;
            return this;
        }

        public Builder entryNode(MyLinkedList.Node val) {
            entryNode = val;
            return this;
        }

        public CircleResult build() {
            return new CircleResult(this);
        }
    }
}

学习时间

2022年5月24日

  • 下午4点-下午6点
  • 晚上8点-晚上9点

学习产出:

  • 本文档链表基础知识和基础操作
  • 力扣21题,合并两个有序链表
  • 力扣23题,合并k个有序链表

今日刷题

21.合并两个有序链表
在这里插入图片描述

  • tips: var是一个可变变量,这是一个可以通过重新分配来更改为另一个值的变量。这种声明变量的方式和java中声明变量的方式一样。
  • tips: val是一个只读变量,这种声明变量的方式相当于java中的final变量。一个val创建的时候必须初始化,因为以后不能被改变。
  • tips: 这个算法的逻辑类似于「拉拉链」,l1, l2 类似于拉链两侧的锯齿,指针 p 就好像拉链的拉索,将两个有序链表合并。
  • tips: 代码中还用到一个链表的算法题中是很常见的「虚拟头结点」技巧,也就是 dummy 节点,它相当于是个占位符,可以避免处理空指针的情况,降低代码的复杂性。
class Solution {
    public ListNode mergeTwoLists(ListNode l1, ListNode l2) {
        // 虚拟头结点
        ListNode dummy = new ListNode(-1), p = dummy;
        ListNode p1 = l1, p2 = l2;

        while (p1 != null && p2 != null) {
            // 比较 p1 和 p2 两个指针
            // 将值较小的的节点接到 p 指针
            if (p1.val > p2.val) {
                p.next = p2;
                p2 = p2.next;
            } else {
                p.next = p1;
                p1 = p1.next;
            }
            // p 指针不断前进
            p = p.next;
        }
        if (p1 != null) {
            p.next = p1;
        }
        if (p2 != null) {
            p.next = p2;
        }
        return dummy.next;
    }
}

23.合并k个升序链表
在这里插入图片描述

  • 合并 k 个有序链表的逻辑类似合并两个有序链表,难点在于,如何快速得到 k 个节点中的最小节点,接到结果链表上?这里我们就要用到 优先级队列(二叉堆) 这种数据结构,把链表节点放入一个最小堆,就可以每次获得 k 个节点中的最小节点。

  • 这个算法是面试常考题,它的时间复杂度是多少呢?
    优先队列 pq 中的元素个数最多是 k,所以一次 poll 或者 add 方法的时间复杂度是 O(logk);所有的链表节点都会被加入和弹出 pq,所以算法整体的时间复杂度是 O(Nlogk),其中 k 是链表的条数,N 是这些链表的节点总数。

class Solution {
    public ListNode mergeKLists(ListNode[] lists) {
        if (lists.length == 0) return null;
        // 虚拟头结点
        ListNode dummy = new ListNode(-1);
        ListNode p = dummy;
        // 优先级队列,最小堆
        PriorityQueue<ListNode> pq = new PriorityQueue<>(
            lists.length, (a, b)->(a.val - b.val));
        // 将 k 个链表的头结点加入最小堆
        for (ListNode head : lists) {
            if (head != null)
                pq.add(head);
        }

        while (!pq.isEmpty()) {
            // 获取最小节点,接到结果链表中
            ListNode node = pq.poll();
            p.next = node;
            if (node.next != null) {
                pq.add(node.next);
            }
            // p 指针不断前进
            p = p.next;
        }
        return dummy.next;
    }
}

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值