【算法训练-链表 七】【链表排序】:链表排序、链表的奇偶重排、重排链表

简介: 【算法训练-链表 七】【链表排序】:链表排序、链表的奇偶重排、重排链表

废话不多说,喊一句号子鼓励自己:程序员永不失业,程序员走向架构!本篇Blog的主题是【链表的排序】,使用【链表】这个基本的数据结构来实现,这个高频题的站点是:CodeTop,筛选条件为:目标公司+最近一年+出现频率排序,由高到低的去牛客TOP101去找,只有两个地方都出现过才做这道题(CodeTop本身汇聚了LeetCode的来源),确保刷的题都是高频要面试考的题。

以及重排链表

附上题目链接,后期可以依据解题思路反复快速练习,题目按照题干的基本数据结构分类,且每个分类的第一篇必定是对基础数据结构的介绍

链表排序【MID】

单链表的升序排列

题干

解题思路

而链表中我们也可以用【合并K个有序链表】同样的方式,当链表被划分到只剩一个节点时,就一定有序。只需要找到中间个元素的前一个节点,将其断开,就可以将链表分成两个子链表,然后继续划分,直到最小,然后往上依次合并。

  • 终止条件: 当子链表划分到为空或者只剩一个节点时,不再继续划分,往上合并。
  • 返回值: 每次返回两个排好序且合并好的子链表。
  • 本级任务: 找到这个链表的中间节点,从前面断开,分为左右两个子链表,进入子问题排序。

怎么找中间元素呢?我们可以使用快慢双指针,快指针每次两步,慢指针每次一步,那么快指针到达链表尾的时候,慢指针正好走了快指针距离的一半,为中间元素。

具体做法:

  1. step 1【入参判断】:首先判断链表为空或者只有一个元素,直接就是有序的。
  2. step 2【找中间节点】:准备三个指针,快指针right每次走两步,慢指针mid每次走一步,前序指针left每次跟在mid前一个位置。三个指针遍历链表,当快指针到达链表尾部的时候,慢指针mid刚好走了链表的一半,正好是中间位置。
  3. step 3【划分子问题】:从left位置将链表断开,刚好分成两个子问题开始递归。
  4. step 4【合并子问题】:将子问题得到的链表合并,参考合并两个有序链表。

代码实现

给出代码实现基本档案

基本数据结构链表

辅助数据结构

算法分治

技巧双指针(快慢指针)

其中数据结构、算法和技巧分别来自:

  • 10 个数据结构:数组、链表、栈、队列、散列表、二叉树、堆、跳表、图、Trie 树
  • 10 个算法:递归、排序、二分查找、搜索、哈希算法、贪心算法、分治算法、回溯算法、动态规划、字符串匹配算法
  • 技巧:双指针、滑动窗口、中心扩散

当然包括但不限于以上

import java.util.*;
/*
 * public class ListNode {
 *   int val;
 *   ListNode next = null;
 *   public ListNode(int val) {
 *     this.val = val;
 *   }
 * }
 */
public class Solution {
    /**
     * 代码中的类名、方法名、参数名已经指定,请勿修改,直接返回方法规定的值即可
     *
     *
     * @param head ListNode类 the head node
     * @return ListNode类
     */
    public ListNode sortInList (ListNode head) {
        // 1 入参判断,如果链表只剩一个节点或者空,则直接有序,到达终止条件
        if (head == null || head.next == null) {
            return head;
        }
        // 2 中间位置为慢指针的下一个节点,奇数节点链表为正中间,偶数则为另一半的开头,所以后半段一定是mid.next
        ListNode mid= findMidNode(head);
        // 3 设置后半段链表,断开前后半段链表
        ListNode newHead = mid.next;
        mid.next = null;
        // 4 合并两段有序链表
        return mergeSortList(sortInList(head), sortInList(newHead));
    }
    // 寻找链表中间节点
    private ListNode findMidNode(ListNode head) {
        ListNode fast = head;
        ListNode slow = head;
        while (fast.next != null && fast.next.next != null) {
            fast = fast.next.next;
            slow = slow.next;
        }
        // 3 中间位置为慢指针的下一个节点,奇数节点链表为正中间,偶数则为另一半的开头
        return slow;
    }
    // 合并两个有序链表
    private ListNode mergeSortList(ListNode pHead1, ListNode pHead2) {
        // 1 入参判断
        if (pHead1 == null && pHead2 == null) {
            return null;
        }
        if (pHead1 == null) {
            return pHead2;
        }
        if (pHead2 == null) {
            return pHead1;
        }
        // 2 设置结果链表
        ListNode dummy = new ListNode(-1);
        ListNode p = dummy;
        while (pHead1 != null && pHead2 != null) {
            if (pHead1.val <= pHead2.val) {
                p.next = pHead1;
                pHead1 = pHead1.next;
            } else {
                p.next = pHead2;
                pHead2 = pHead2.next;
            }
            p = p.next;
        }
        // 3 如果其中一个链表为空,后续直接接另一个
        if (pHead1 == null) {
            p.next = pHead2;
        }
        if (pHead2 == null) {
            p.next = pHead1;
        }
        return dummy.next;
    }
}

复杂度分析

链表的奇偶重排【MID】

需要注意的是节点的位置而非节点的值

题干

解题思路

核心思路就是奇数位节点和偶数位节点断掉重连

  1. step 1:判断空链表的情况,如果链表为空,不用重排。如果链表只有一个节点也不用重排
  2. step 2:使用双指针odd和even分别遍历奇数节点和偶数节点,并给偶数节点链表一个头。
  3. step 3:上述过程,每次遍历两个节点,且even在后面,因此每轮循环用even检查后两个元素是否为NULL,如果不为再进入循环行上述连接过程。
  4. step 4:将偶数节点头接在奇数最后一个节点后,再返回头部。

代码实现

给出代码实现基本档案

基本数据结构链表

辅助数据结构

算法迭代

技巧双指针(同向指针)

其中数据结构、算法和技巧分别来自:

  • 10 个数据结构:数组、链表、栈、队列、散列表、二叉树、堆、跳表、图、Trie 树
  • 10 个算法:递归、排序、二分查找、搜索、哈希算法、贪心算法、分治算法、回溯算法、动态规划、字符串匹配算法
  • 技巧:双指针、滑动窗口、中心扩散

当然包括但不限于以上

import java.util.*;
/*
 * public class ListNode {
 *   int val;
 *   ListNode next = null;
 *   public ListNode(int val) {
 *     this.val = val;
 *   }
 * }
 */
public class Solution {
    /**
     * 代码中的类名、方法名、参数名已经指定,请勿修改,直接返回方法规定的值即可
     *
     *
     * @param head ListNode类
     * @return ListNode类
     */
    public ListNode oddEvenList (ListNode head) {
        // 1 入参条件判断
        if (head == null) {
            return null;
        }
        if (head.next == null) {
            // 只有1个节点,不用重排
            return head;
        }
        // 2 设置偶数虚拟头节点,并设定奇偶双指针
        ListNode odd = head;
        ListNode evenDummy = head.next;
        ListNode even = evenDummy;
        // 3 遍历链表,往奇数和偶数节点上补充对应节点,因为even在后边,所以要以even做判断,否则对于1-2-3-4这种情况
        //odd结束时指向null,null.next会有问题,返回仍然是null
        while (even != null && even.next != null) {
            // 奇数位断开指向偶数的指针并指向下一个奇数位
            odd.next = even.next;
            odd = odd.next;
            // 偶数位断开指向奇数的指针并指向下一个偶数位
            even.next = odd.next;
            even = even.next;
        }
        // 4 遍历完后偶数整体接到奇数后
        odd.next = evenDummy;
        return head;
    }
}

复杂度分析

时间复杂度:O(n),遍历一次链表的所有节点

空间复杂度:O(1),常数级指针,无额外辅助空间

重排链表【MID】

复杂度升级了一些,不过套路类似

题干

解题思路

注意到目标链表即为将原链表的左半端和反转后的右半端合并后的结果。这样我们的任务即可划分为三步:

  1. 找到中点】找到原链表的中点(参考「876. 链表的中间结点」)。我们可以使用快慢指针来 O(N)O(N)O(N) 地找到链表的中间节点。
  2. 反转右半边】将原链表的右半端反转(参考「206. 反转链表」)。我们可以使用迭代法实现链表的反转。
  3. 合并两个链表】将原链表的两端合并。因为两链表长度相差不超过 11,因此直接合并即可。

以上需要注意的是,找链表中点时,对于奇数个节点,中点为前半段链表所有(从题目示例可以看出)

代码实现

给出代码实现基本档案

基本数据结构链表

辅助数据结构

算法迭代

技巧双指针

其中数据结构、算法和技巧分别来自:

  • 10 个数据结构:数组、链表、栈、队列、散列表、二叉树、堆、跳表、图、Trie 树
  • 10 个算法:递归、排序、二分查找、搜索、哈希算法、贪心算法、分治算法、回溯算法、动态规划、字符串匹配算法
  • 技巧:双指针、滑动窗口、中心扩散

当然包括但不限于以上

/**
 * Definition for singly-linked list.
 * public class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode() {}
 *     ListNode(int val) { this.val = val; }
 *     ListNode(int val, ListNode next) { this.val = val; this.next = next; }
 * }
 */
class Solution {
    public void reorderList(ListNode head) {
        // 1 入参校验
        if (head == null || head.next == null) {
            return ;
        }
        // 2 找到链表中点,断开后半段链表,这次要找的mid要偏右一些,前半段保留偏长,这样后续交替衔接才没问题
        ListNode mid = middleNode(head);
        ListNode l1 = head;
        ListNode l2 = mid.next;
        mid.next = null;
        // 3 反转后半段链表
        l2 = reverse(l2);
        // 4 将反转后链表连接到原链表中
        mergeList(l1, l2);
    }
    // 交替合并两个链表
    private void mergeList(ListNode l1, ListNode l2) {
        ListNode pNext1;
        ListNode pNext2;
        // 因为两个链表仅仅相差1,所以直接交替合并即可
        while (l1 != null && l2 != null) {
            // 暂存两个链表的下一个节点
            pNext1 = l1.next;
            pNext2 = l2.next;
            // 交替衔接l1和l2
            l1.next = l2;
            l1 = pNext1;
            l2.next = l1;
            l2 = pNext2;
        }
    }
    // 寻找中间节点
    private ListNode middleNode(ListNode head) {
        ListNode slow = head;
        ListNode fast = head;
        while (fast.next != null && fast.next.next != null) {
            slow = slow.next;
            fast = fast.next.next;
        }
        return slow;
    }
    // 反转链表
    private ListNode reverse(ListNode head) {
        ListNode pre = null;
        ListNode cur = head;
        while (cur != null) {
            ListNode pNext = cur.next;
            cur.next = pre;
            pre = cur;
            cur = pNext;
        }
        return pre;
    }
}

复杂度分析

时间复杂度:遍历了链表,时间复杂度为O(N)

空间复杂度:没有使用额外空间,空间复杂度为O(1)

相关文章
|
1月前
|
分布式计算 Java 开发工具
阿里云MaxCompute-XGBoost on Spark 极限梯度提升算法的分布式训练与模型持久化oss的实现与代码浅析
本文介绍了XGBoost在MaxCompute+OSS架构下模型持久化遇到的问题及其解决方案。首先简要介绍了XGBoost的特点和应用场景,随后详细描述了客户在将XGBoost on Spark任务从HDFS迁移到OSS时遇到的异常情况。通过分析异常堆栈和源代码,发现使用的`nativeBooster.saveModel`方法不支持OSS路径,而使用`write.overwrite().save`方法则能成功保存模型。最后提供了完整的Scala代码示例、Maven配置和提交命令,帮助用户顺利迁移模型存储路径。
|
1月前
|
算法 安全 搜索推荐
2024重生之回溯数据结构与算法系列学习之单双链表精题详解(9)【无论是王道考研人还是IKUN都能包会的;不然别给我家鸽鸽丢脸好嘛?】
数据结构王道第2.3章之IKUN和I原达人之数据结构与算法系列学习x单双链表精题详解、数据结构、C++、排序算法、java、动态规划你个小黑子;这都学不会;能不能不要给我家鸽鸽丢脸啊~除了会黑我家鸽鸽还会干嘛?!!!
|
1月前
|
存储 Web App开发 算法
2024重生之回溯数据结构与算法系列学习之单双链表【无论是王道考研人还是IKUN都能包会的;不然别给我家鸽鸽丢脸好嘛?】
数据结构之单双链表按位、值查找;[前后]插入;删除指定节点;求表长、静态链表等代码及具体思路详解步骤;举例说明、注意点及常见报错问题所对应的解决方法
|
2月前
|
存储 缓存 算法
经典算法之链表篇(三)
经典算法之链表篇(三)
|
2月前
|
算法
经典算法之链表篇(二)
经典算法之链表篇(二)
|
2月前
|
算法 索引
经典算法之链表篇
经典算法之链表篇
|
2月前
|
算法
❤️算法笔记❤️-(每日一刷-160、相交链表)
❤️算法笔记❤️-(每日一刷-160、相交链表)
19 1
|
2月前
|
算法
❤️算法笔记❤️-(每日一刷-83、删除排序链表中的重复项)
❤️算法笔记❤️-(每日一刷-83、删除排序链表中的重复项)
34 0
|
6天前
|
机器学习/深度学习 算法
基于改进遗传优化的BP神经网络金融序列预测算法matlab仿真
本项目基于改进遗传优化的BP神经网络进行金融序列预测,使用MATLAB2022A实现。通过对比BP神经网络、遗传优化BP神经网络及改进遗传优化BP神经网络,展示了三者的误差和预测曲线差异。核心程序结合遗传算法(GA)与BP神经网络,利用GA优化BP网络的初始权重和阈值,提高预测精度。GA通过选择、交叉、变异操作迭代优化,防止局部收敛,增强模型对金融市场复杂性和不确定性的适应能力。
124 80
|
2天前
|
机器学习/深度学习 数据采集 算法
基于PSO粒子群优化的CNN-GRU-SAM网络时间序列回归预测算法matlab仿真
本项目展示了基于PSO优化的CNN-GRU-SAM网络在时间序列预测中的应用。算法通过卷积层、GRU层、自注意力机制层提取特征,结合粒子群优化提升预测准确性。完整程序运行效果无水印,提供Matlab2022a版本代码,含详细中文注释和操作视频。适用于金融市场、气象预报等领域,有效处理非线性数据,提高预测稳定性和效率。