148.排序链表

news2024/11/15 15:35:55

148.排序链表

题目:

给你链表的头结点 head ,请将其按 升序 排列并返回 排序后的链表

示例 1:

输入:head = [4,2,1,3]
输出:[1,2,3,4]

示例 2:

输入:head = [-1,5,3,4,0]
输出:[-1,0,3,4,5]

示例 3:

输入:head = []
输出:[]

提示:

  • 链表中节点的数目在范围 [0, 5 * 104]
  • -105 <= Node.val <= 105

进阶: 你可以在 O(n log n) 时间复杂度和常数级空间复杂度下,对链表进行排序吗?

解答一:归并排序(递归法)

1.题目要求时间空间复杂度分别为O(nlogn)和O(1),根据时间复杂度我们自然想到二分法,从而联想到归并排序;

​ 2.对数组做归并排序的空间复杂度为 O(n),分别由新开辟数组O(n)和递归函数调用O(logn)组成,而根据链表特性:

​ a.数组额外空间:链表可以通过修改引用来更改节点顺序,无需像数组一样开辟额外空间;
​ b.递归额外空间:递归调用函数将带来O(logn)的空间复杂度,因此若希望达到O(1)空间复杂度,则不能使用递归。

3.通过递归实现链表归并排序,有以下两个环节:

分割 cut 环节: 找到当前链表中点,并从中点将链表断开(以便在下次递归 cut 时,链表片段拥有正确边界);
​ a.我们使用 fast,slow 快慢双指针法,奇数个节点找到中点,偶数个节点找到中心左边的节点。
​ b.找到中点 slow 后,执行 slow.next = None 将链表切断。
​ c.递归分割时,输入当前链表左端点 head 和中心节点 slow 的下一个节点 tmp(因为链表是从 slow 切断的)。
cut 递归终止条件: 当head.next == None时,说明只有一个节点了,直接返回此节点。
4.合并 merge 环节: 将两个排序链表合并,转化为一个排序链表。
​ a.双指针法合并,建立辅助ListNode h 作为头部。
​ b.设置两指针 left, right 分别指向两链表头部,比较两指针处节点值大小,由小到大加入合并链表头部,指针交替前进,直至添加完两个链表。
​ c.返回辅助ListNode h 作为头部的下个节点 h.next。
​ d.时间复杂度 O(l + r),l, r 分别代表两个链表长度。
5.当题目输入的 head == None 时,直接返回None。

在这里插入图片描述

 public ListNode sortList(ListNode head) {
        // 1、递归结束条件
        if (head == null || head.next == null) {
            return head;
        }

        // 2、找到链表中间节点并断开链表 & 递归下探
        ListNode midNode = middleNode(head);
        ListNode rightHead = midNode.next;
        midNode.next = null;

        ListNode left = sortList(head);
        ListNode right = sortList(rightHead);

        // 3、当前层业务操作(合并有序链表)
        return mergeTwoLists(left, right);
    }
    
    //  找到链表中间节点(876. 链表的中间结点)
    private ListNode middleNode(ListNode head) {
        if (head == null || head.next == null) {
            return head;
        }
        ListNode slow = head;
        ListNode fast = head.next.next;

        while (fast != null && fast.next != null) {
            slow = slow.next;
            fast = fast.next.next;
        }

        return slow;
    }

    // 合并两个有序链表(21. 合并两个有序链表)
    private ListNode mergeTwoLists(ListNode l1, ListNode l2) {
        ListNode sentry = new ListNode(-1);
        ListNode curr = sentry;

        while(l1 != null && l2 != null) {
            if(l1.val < l2.val) {
                curr.next = l1;
                l1 = l1.next;
            } else {
                curr.next = l2;
                l2 = l2.next;
            }

            curr = curr.next;
        }

        curr.next = l1 != null ? l1 : l2;
        return sentry.next;
    }

解答二:归并排序(从底至顶直接合并)

对于非递归的归并排序,需要使用迭代的方式替换cut环节:
我们知道,cut环节本质上是通过二分法得到链表最小节点单元,再通过多轮合并得到排序结果。
每一轮合并merge操作针对的单元都有固定长度intv,例如:
第一轮合并时intv = 1,即将整个链表切分为多个长度为1的单元,并按顺序两两排序合并,合并完成的已排序单元长度为2。
第二轮合并时intv = 2,即将整个链表切分为多个长度为2的单元,并按顺序两两排序合并,合并完成已排序单元长度为4。
以此类推,直到单元长度intv >= 链表长度,代表已经排序完成。
根据以上推论,我们可以仅根据intv计算每个单元边界,并完成链表的每轮排序合并,例如:
当intv = 1时,将链表第1和第2节点排序合并,第3和第4节点排序合并,……。
当intv = 2时,将链表第1-2和第3-4节点排序合并,第5-6和第7-8节点排序合并,……。
当intv = 4时,将链表第1-4和第5-8节点排序合并,第9-12和第13-16节点排序合并,……。
此方法时间复杂度O(nlogn),空间复杂度O(1)。

在这里插入图片描述

class Solution {
    public ListNode sortList(ListNode head) {
        ListNode h, h1, h2, pre, res;
        h = head;
        int length = 0, intv = 1;
        while (h != null) {
            h = h.next;
            length++;
        }
        res = new ListNode(0);
        res.next = head;
        while (intv < length) {
            pre = res;
            h = res.next;
            while (h != null) {
                int i = intv;
                h1 = h;
                while (i > 0 && h != null) {
                    h = h.next;
                    i--;
                }
                if (i > 0) break;
                i = intv;
                h2 = h;
                while (i > 0 && h != null) {
                    h = h.next;
                    i--;
                }
                int c1 = intv, c2 = intv - i;
                while (c1 > 0 && c2 > 0) {
                    if (h1.val < h2.val) {
                        pre.next = h1;
                        h1 = h1.next;
                        c1--;
                    } else {
                        pre.next = h2;
                        h2 = h2.next;
                        c2--;
                    }
                    pre = pre.next;
                }
                pre.next = c1 == 0 ? h2 : h1;
                while (c1 > 0 || c2 > 0) {
                    pre = pre.next;
                    c1--;
                    c2--;
                }
                pre.next = h;
            }
            intv *= 2;
        }
        return res.next;
    }
}

解法三:快排

public static ListNode sortList(ListNode head) {

        if(head==null || head.next==null){
            return head;
        }
        ArrayList<Integer> data = new ArrayList();
        ListNode pre = head;
        int len =0;
        while(pre!=null){
            data.add(pre.val);
            pre=pre.next;
            len++;
        }
        int[] array = data.stream().mapToInt(Integer::intValue).toArray();
        quickSort( array,0,len-1);

        ListNode first = new ListNode( -1 );
        ListNode result = first;
        for(int i=0;i<len;i++){
            head = head.next;
            ListNode temp = new ListNode( array[i]);
            first.next = temp;
            first = temp;
        }
        return result.next;

    }


    public static void quickSort(int[] array, int low, int high) {

        /**
         * 分析:
         * 1.选定一个基准值,array[low]
         * 2.右指针从右向左遍历high--,查找比基准值小的数据,左指针从左向右low++,查找比基准值大的数据
         * 3.如果指针未相遇,交换左右两值位置,如果指针相遇,则替换基准值的位置
         * 4.左递归,右递归
         */
        // 方法退出条件,指针相遇或错过
        if (low >= high) {
            return;
        }
        // 1. 指定基准值和左右指针记录位置
        int pivot = array[low];
        int l = low;
        int r = high;
        int temp = 0;
        // 2. 遍历条件,左右指针位置
        while (l < r) {
            // 2.1 右侧遍历
            while (l < r && array[r] >= pivot) {
                r--;
            }
            // 2.2 左侧遍历
            while (l < r && array[l] <= pivot) {
                l++;
            }
            // 2.3 l指针还在r指针左侧,尚未相遇
            if (l < r) {
                temp = array[l];
                array[l] = array[r];
                array[r] = temp;
            }
        }
        // 3. 当左右指针相遇,交换基准值位置
        array[low] = array[l];
        array[l] = pivot;
        // 4. 根据条件左侧递归遍历
        if (low < l) {
            quickSort(array, low, l - 1);
        }
        // 5. 根据条件右侧递归遍历
        if (r < high) {
            quickSort(array, r + 1, high);
        }
    }

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/131589.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【论文阅读】CVPR2018-深度材料感知跨光谱立体匹配

深度材料感知跨光谱立体匹配 摘要 跨光谱成像对识别和检测任务很有帮助。通常&#xff0c;多个相机用于跨光谱成像&#xff0c;因此需要图像对齐或双目系统中的视差估计。多相机跨光谱系统逐渐被嵌入到有源RGB-D设备中&#xff08;例如Kinect和iPhone X中的RGB-NIR相机&#…

2022 年度回忆

2022 年度回忆 过了今天就是2023年了&#xff0c;记录一下在这一年里发生的点点滴滴吧。 年度总结2022 年度回忆1.石家庄实习2.准备秋招&#xff0c;然后去沈阳实习3.回学校4.来北京实习了总结今年大体且分为四条故事线 1.22年上半年石家庄实习 2.实习结束回家准备秋招&#…

句子表征(各项异性等偏差):PromptBERT: Improving BERT Sentence Embeddings with Prompts

一、核心 句子表征存在不足之处&#xff0c;可能面临各向异性、可能受到词频的影响、可能受到子词、大小写等的影响等等。 Gao et al.(2019)和Wang et al.(2020)指出&#xff0c;对于语言建模&#xff0c;使用最大似然训练通常会产生一个各向异性的词嵌入空间。“各向异性”是…

BabaSSL:支持半同态加密算法 EC-ElGamal

01 背 景 随着大数据与人工智能的快速发展&#xff0c;个人隐私数据泄露和滥用时有发生&#xff0c;隐私安全问题也越来越被重视。 国家于 2020 年施行密码法、2021 年施行个人信息保护法&#xff0c;对个人隐私数据和数据安全加密有更高的要求。 因此&#xff0c;隐私计算也…

2022年博客之路总结

今年是不平凡的一年&#xff0c;IT行业 开卷 的一年&#xff0c;今年大多数人 都 因种种原因 被迫换了工作&#xff0c;再次 先感谢CSDN 这个平台&#xff0c;在这里 给自己了一块可以展示自己才华的空间&#xff0c;通过CSDN平台的各项运营数据&#xff0c;让我有幸 拿到了 更…

flv.js播放flv视频

flv.js是FLV视频播放器&#xff0c;纯JS开发&#xff0c;无需Flash。 <!DOCTYPE html> <html><head><meta charset"UTF-8"><meta http-equiv"X-UA-Compatible" content"IEedge"><meta name"viewport"…

【虚幻引擎UE】UE5 制作一个元旦烟花短视频的小案例(使用sequence制作视频案例)

祝愿大家元旦快乐&#xff01; 效果预览 一、创建粒子烟花特效 可以使用现成的Niagara烟花粒子特效&#xff0c;直接跳过这一步。 1、 通过Niagara系统创建粒子特效 选择现有发射器素材。 或者也可新建空白特效&#xff0c;将发射器拖入轨道&#xff08;素材包含闪光、拖…

基于TP6+Uni-app框架开发的多端圈子社区论坛小程序H5系统,带数据库和安装教程

正文&#xff1a; 前台uni-app后台tp6开发的多端圈子社区论坛小程序H5系统,带数据库和安装教程。 系统基于TP6Uni-app框架开发&#xff1b;客户移动端采用uni-app开发&#xff0c;管理后台TH6开发。 系统支持微信公众号端、微信小程序端、H5端、PC端多端账号同步&#xff0c…

CSDN的2022和2023

前言 今天是2022年12月31日&#xff0c;今年的最后一天&#xff0c;年关已至。 又到了&#xff1a;回头看路&#xff0c;低头赶路&#xff0c;抬头望路的时候。 回顾2022 疫情中的2022 今年应该算是疫情的高峰期吧&#xff0c;各种新冠变异株横行&#xff0c;从严控到一夜…

Day845.Fork/Join -Java 并发编程实战

Fork/Join Hi&#xff0c;我是阿昌&#xff0c;今天学习记录的是关于Fork/Join的内容。 线程池、Future、CompletableFuture 和 CompletionService&#xff0c;仔细观察会发现这些工具类都是在帮助站在任务的视角来解决并发问题&#xff0c;而不是让纠缠在线程之间如何协作的…

深度学习:05 卷积神经网络介绍(CNN)

目录 卷积神经网络简介 为什么要用卷积神经网络 网络结构组成 卷积层 卷积计算 卷积核大小 f 边界填充 (p)adding 步长 (s)tride 计算公式 卷积层 激活函数 池化层&#xff08;pooling&#xff09; dropout层 全连接层 卷积神经网络简介 卷积神经网络由一个或多个…

DoIP协议从入门到精通系列——车载网络安全

现代社会慢慢步入数字时代,在这个时代,网络安全已经成为最重要的关注点。自从1980年第一次出现电脑病毒,网络威胁和攻击持续不断,给社会(经济)带来巨大影响。随着汽车的数字化和互联化发展,自然而然会联想到汽车也将为成为黑客攻击的目标。导致的问题除了单纯的不便(攻…

数据结构 | 十大排序超硬核八万字详解【附动图演示、算法复杂度性能分析】

写在前面 2023年的第一篇博客&#xff0c;在这里先祝大家兔年快乐&#x1f430; 本文从学习到搜寻各种资料&#xff0c;整理成博客的形式展现足足花了一个月的时间&#xff0c;慢工出细活&#xff0c;希望本篇文章可以真正带你学懂排序&#xff0c;不再为写排序算法而苦恼 博主…

MQTT协议的工作原理

一、MQTT概述 MQTT由IBM的Andy Stanford-Clark博士和Arcom&#xff08;现为Eurotech&#xff09;的Arlen Nipper于1999年发明。 MQTT 是物联网 &#xff08;IoT&#xff09; 最常用的消息传递协议。MQTT 代表 MQ 遥测传输。该协议是一组规则&#xff0c;用于定义物联网设备如何…

二叉树16:找树左下角的值

主要是我自己刷题的一些记录过程。如果有错可以指出哦&#xff0c;大家一起进步。 转载代码随想录 原文链接&#xff1a; 代码随想录 leetcode链接&#xff1a;513.找树左下角的值 题目&#xff1a; 给定一个二叉树的 根节点 root&#xff0c;请找出该二叉树的 最底层 最左边…

Android每周一轮子:Nvwa(热修复)

前言 &#xff08;废话&#xff09; 最近发现了一个问题&#xff0c;一些平时博客写的很多的程序员&#xff0c;反倒在日常的工作中&#xff0c;却是业务写的很一般&#xff0c;只会摆理论的人&#xff0c;甚至还跑出来教别人如何找工作&#xff0c;如何做架构&#xff0c;其实…

2022年终总结:少年不惧岁月长,彼方尚有荣光在。

2022年终总结&#xff1a;少年不惧岁月长&#xff0c;彼方尚有荣光在。 &#x1f3ac; 博客主页&#xff1a;王同学要努力 &#x1f3ac;个人简介&#xff1a;大三小白&#xff0c;喜欢前端 &#xff0c;热爱分享 &#x1f3a5; 本文由 王同学要努力 原创&#xff0c;首发于…

Eureka 注册中心

Eureka 注册中心目录概述需求&#xff1a;设计思路实现思路分析1.快速上手2.增加 Maven 依赖3.Client端配置注册中心Server端配置注册中心参考资料和推荐阅读Survive by day and develop by night. talk for import biz , show your perfect code,full busy&#xff0c;skip ha…

R 语言 管道操作符

背景 关于代码的简洁性,就是你使用了比较简化的高级操作符,但是有时候会增加代码的可读性。这种可读性在于你是否真正的去了解R的高级语法。你了解高级语法,他就不难,你不了解,他就难,可读性差。 这里我们来讲解一下,关于管道操作符,使R语言编程简化一些。 管道操作…

【登录流程执行逻辑】

1)整体流程图 2.main.js是入口&#xff0c;里面require("permission")时会触发方法体 在进行require("权限时")&#xff0c;会进行一系列初始化。 重定向。 接着走login方法。 vuex其实就是store。 触发vuex里面的方法: await store.dispatch(user/getInf…