(Java)leetcode-148 Sort List(排序链表)

2023-11-15

题目描述

在 O(n log n) 时间复杂度和常数级空间复杂度下,对链表进行排序。

示例 1:

输入: 4->2->1->3
输出: 1->2->3->4
示例 2:

输入: -1->5->3->4->0
输出: -1->0->3->4->5

思路1:递归

如果不要求O(1)的空间复杂度,仅仅考虑 O(n log n) 时间复杂度,那么可以采用归并排序。
注意:递归调用函数将带来O(logn)的空间复杂度。

首先将链表分割,关键是找中点,通过快慢指针的方法找到中点,进行分割。
接着递归地往下分割,直到无法再分,往上层返回,在上层将有序链表合并。
在这里插入图片描述
(图片来自Krahets的题解)

代码

public class Solution {
  
  public ListNode sortList(ListNode head) {
  	// 1、递归结束条件
    if (head == null || head.next == null)
      return head;
        
    // 2、使用双指针法找到中间节点,并将链表分为前后两部分
    ListNode prev = null, slow = head, fast = head;
    
    while (fast != null && fast.next != null) {
      prev = slow;
      slow = slow.next;
      fast = fast.next.next;
    }
    // pre为中间节点(前半部分的最后一个节点)
    prev.next = null;
    
    // 3、每个部分分别排序
    ListNode l1 = sortList(head);
    ListNode l2 = sortList(slow);
    
    // 4、合并已经有序的两部分链表
    return merge(l1, l2);
  }
  
  // 用于合并有序的两个链表
  ListNode merge(ListNode l1, ListNode l2) {
    ListNode newHead = new ListNode(0), p = newHead;
    
    while (l1 != null && l2 != null) {
      if (l1.val < l2.val) {
        p.next = l1;
        l1 = l1.next;
      } else {
        p.next = l2;
        l2 = l2.next;
      }
      p = p.next;
    }
    
    p.next = l1 != null ? l1 : l2;  
    return newHead.next;
  }

}

执行用时:4 ms, 在所有 Java 提交中击败了61.20%的用户
内存消耗:41.4 MB, 在所有 Java 提交中击败了5.88%的用户

思路2:非递归(从底至顶直接合并)

上面已经分析过,递归的算法时间复杂度无法满足常数级别,因此要考虑非递归的解法。

对于非递归的归并排序,需要使用迭代的方式替换cut环节:

我们知道,cut环节本质上是通过二分法得到链表最小节点单元,再通过多轮合并得到排序结果。

每一轮合并merge操作针对的单元都有固定长度intv,例如:
第一轮合并时 i = 1,即将整个链表切分为多个长度为1的单元,并按顺序两两排序合并,合并完成的已排序单元长度为2。
第二轮合并时 i = 2,即将整个链表切分为多个长度为2的单元,并按顺序两两排序合并,合并完成已排序单元长度为4。
以此类推,直到单元长度 i >= 链表长度,代表已经排序完成。

根据以上推论,我们可以仅根据 i 计算每个单元边界,并完成链表的每轮排序合并,例如:

当 i = 1时,将链表第1和第2节点排序合并,第3和第4节点排序合并,……。
当 i = 2时,将链表第1-2和第3-4节点排序合并,第5-6和第7-8节点排序合并,……。
当 i = 4时,将链表第1-4和第5-8节点排序合并,第9-12和第13-16节点排序合并,……。

整个链表总共遍历 logn 次,每次遍历的复杂度是 O(n),所以总时间复杂度是 O(nlogn);
整个算法没有递归,迭代时只会使用常数个额外变量,所以额外空间复杂度是O(1).

在这里插入图片描述
(以上整理自Krahets的题解)

class Solution {
    public ListNode sortList(ListNode head) {
        // 归并排序
        int n = 0;
        // 求链表的长度n
        for(ListNode i = head; i != null; i=i.next) n++;

        ListNode dummy = new ListNode(0);
        dummy.next = head;
        
        
        // 第一层循环,分块,从1个一块,2个一块,4个一块,直到n个一块,
        for(int i = 1; i < n; i = 2*i){
            ListNode begin = dummy;
            // 开始归并
            // j + i < n 表示只有一段就不归并了,因为已经是排好序的
            // 第二层循环,将此时的最小单元排序合并
            for(int j = 0; j + i < n; j = j + 2 * i){
                // 两块,找两块的起始节点
                // 开始都指向第一块的起点
                // 然后second走n步指向第二块的起点
                ListNode first = begin.next, second = first;
                for(int k = 0; k < i; k++) second = second.next;

                // 遍历第一块和第二块进行归并
                // 第一块的数量为i
                // 第二块的数量为i也可能小于i,所以循环条件要加一个second != null
                int f = 0, s = 0;
                while(f < i && s < i && second != null){
                    if(first.val < second.val){
                        begin.next = first;
                        begin = begin.next;
                        first = first.next;
                        f++;
                    }else{
                        begin.next = second;
                        begin = begin.next;
                        second = second.next;
                        s++;
                    }
                }
                // 归并之后可能有多余的没有处理
                // 处理第一块的剩余部分
                while(f < i){
                    begin.next = first;
                    begin = begin.next;
                    first = first.next;
                    f++;

                }
                // 处理第二块的剩余部分
                while(s < i && second != null){
                    begin.next = second;
                    begin = begin.next;
                    // second已经更新到下一块的起点了
                    second = second.next;
                    s++;
                }

                // 更新begin
                // begin.next 指向下一块的起点
                begin.next = second;
            }
        }
        return dummy.next;

    }
}

执行用时:5 ms, 在所有 Java 提交中击败了43.96%的用户
内存消耗:42.2 MB, 在所有 Java 提交中击败了5.88%的用户

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

(Java)leetcode-148 Sort List(排序链表) 的相关文章

  • “源兼容性”和“目标兼容性”有什么区别?

    之间有什么关系 区别sourceCompatibility and targetCompatibility 当它们设置为不同的值时会发生什么 根据工具链和兼容性 https docs gradle org current userguide
  • 以相反的顺序打印任何集合中的项目?

    我在 使用 Java 进行数据结构和问题解决 一书中遇到以下问题 编写一个例程 使用 Collections API 以相反的顺序打印任何 Collection 中的项目 不要使用 ListIterator 我不会把它放在这里 因为我想让有
  • 通过Zuul上传大文件

    我在通过 zuul 上传大文件时遇到问题 我正在使用 apache commons 文件上传 https commons apache org proper commons fileupload https commons apache o
  • 自定义列表字段点击事件

    我正在编写一个应用程序 其中我创建了用于显示列表视图的自定义列表字段 我的 CustomListField 包含连续的一个图像和文本 我正在通过单击列表字段行获取字段更改侦听器 但我也想将字段更改侦听器放在图像上 谁能告诉我我该怎么做 这是
  • 未装饰窗户的 Windows Snap 功能?

    有谁知道如何允许未装饰的窗户使用此功能 唯一的选择就是重新实施它 有任何想法吗 谢谢 可停靠可能是唯一的JToolBar http docs oracle com javase tutorial uiswing components too
  • Java中Gson、JsonElement、String比较

    好吧 我想知道这可能非常简单和愚蠢 但在与这种情况作斗争一段时间后 我不知道发生了什么 我正在使用 Gson 来处理一些 JSON 元素 在我的代码中的某个位置 我将 JsonObject 的 JsonElements 之一作为字符串获取
  • 使用 OkHttp 下载损坏的文件

    我编写的下载文件的方法总是会产生损坏的文件 public static String okDownloadToFileSync final String link final String fileName final boolean te
  • JOOQ 忽略具有默认值的数据库列

    看来JOOQ完全忽略了数据库列的默认值 既不会更新 ActiveRecord 对象 也不会在 INSERT 时跳过此列 相反 它尝试将其设置为 NULL 这在 NOT NULL 列上失败 Example CREATE TABLE bug f
  • 如何使用 Java 引用释放 Java Unsafe 内存?

    Java Unsafe 类允许您按如下方式为对象分配内存 但是使用此方法在完成后如何释放分配的内存 因为它不提供内存地址 Field f Unsafe class getDeclaredField theUnsafe Internal re
  • 为什么在将 String 与 null 进行比较时会出现 NullPointerException?

    我的代码在以下行中出现空指针异常 if stringVariable equals null 在此语句之前 我声明了 stringVariable 并将其设置为数据库字段 在这个声明中 我试图检测该字段是否有null值 但不幸的是它坏了 有
  • Android 认为我没有关闭数据库!为什么?

    我有一个 SQLiteDatabase 数据成员 我在 onCreate 中初始化它 并在 onPause onStop 和 onDestroy 中调用 close 它在 onResume 中重新初始化 它似乎运行得很好 但当我查看调试器时
  • 在java程序中使用c++ Dll

    我正在尝试使用System LoadLibrary 使用我用 C 编写的一个简单的 dll UseDllInJava java import com sun jna Library import com sun jna Native imp
  • 类更改(例如字段添加或删除)是否保持 Serialized 的向后兼容性?

    我有一个关于 Java 序列化的问题 在这种情况下 您可能需要修改可序列化类并保持向后兼容性 我有丰富的 C 经验 所以请允许我将 Java 与 NET 进行比较 在我的Java场景中 我需要使用Java的运行时序列化机制序列化一个对象 并
  • 我们如何使用 thymeleaf 绑定对象列表的列表

    我有一个表单 用户可以在其中添加任意数量的内容表对象这也可以包含他想要的列对象 就像在 SQL 中构建表一样 我尝试了下面的代码 但没有任何效果 并且当我尝试绑定两个列表时 表单不再出现 控制器 ModelAttribute page pu
  • titledBorder 标题中的图标

    您好 是否可以在 titledBorder 的标题中放置一个图标 例如以下代码 import java awt GridLayout import javax swing JFrame import javax swing JLabel i
  • Java 中清除嵌套 Map 的好方法

    public class MyCache AbstractMap
  • 带 getClassLoader 和不带 getClassLoader 的 getResourceAsStream 有什么区别?

    我想知道以下两者之间的区别 MyClass class getClassLoader getResourceAsStream path to my properties and MyClass class getResourceAsStre
  • 什么是 Java2D 处理程序线程?

    我创建了一个使用 Hibernate 的示例 java 应用程序 当我进行线程转储时 我观察到一个名为 Java2D Disposer 的奇怪线程 有人能告诉我该线程的功能吗 AWT 系统中的某些实体需要最终确定以释放资源 最突出的例子是j
  • Spring 作为 JNDI 提供者?

    我想使用 Spring 作为 JNDI 提供程序 这意味着我想在 Spring 上下文中配置一个 bean 可以通过 JNDI 访问该 bean 这看起来像这样
  • GAE 无法部署到 App Engine

    我正在尝试从 Eclipse 发布 Web 应用程序 我在 GAE 上创建了四个项目 可以通过登录我的帐户并查看控制台来查看它们 我已经改变了appengine web xml到项目的应用程序 ID 如果我将其更改为 GAE 上第一个创建的

随机推荐