从零学算法148

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 \* 10^4^

-105 <= Node.val <= 105

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

  • 根据这个时间复杂度能想到,不断二分的排序,那就是用归并排序了,归并的原理也就是不断递归直到单个有序(只有一个节点)后返回,然后返回上一层合并两个有序的(得到 2 个有序的节点),再返回上一层合并两个有序的(得到 4 个有序的节点),直到返回到最开始合并左右两部分链表得到完全有序的链表,先写个伪代码模版
java 复制代码
  public ListNode sortList(ListNode head) {
      return merge(起点,终点);
  }
  ListNode merge(ListNode head,ListNode tail){
      if (只有一个节点) {
          return 该节点;
      }
      ListNode mid = 中点
      return mergeTwo(merge(head,mid),merge(mid,tail));
  }
  // 合并两个有序链表
  ListNode mergeTwo(ListNode a, ListNode b){
  }
  • 这里需要考虑的主要有两点:链表边界情况的考虑以及中点的获取。
  • 这里我们用左闭右开的区间来归并,那么当起点的下一个节点为终点时也就表示我们可用的只剩下起点这一个节点,此时可以结束最内层的递归返回上一层了
  • 而中点的获取,我们采用快慢指针的方式来获取。
java 复制代码
  public ListNode sortList(ListNode head) {
  	  // 排除无需排序的情况
      if(head==null || head.next==null)return head;
      // 左闭右开的区间
      return merge(head, null);
  }
  ListNode merge(ListNode head,ListNode tail){
  	  // 只剩一个可用节点 
      if (head.next == tail) {
          head.next = null;
          return head;
      }
      // 快指针每次走两步,慢指针每次走一步,所以快指针到尾部时慢指针位于中点
      ListNode slow=head, fast=head;
      while(fast!=tail && fast.next!=tail){
          slow=slow.next;
          fast=fast.next.next;
      }
      // 不断分治,最后会分解到得到单个节点才进行合并
      return mergeTwo(merge(head,slow),merge(slow,tail));
  }
  // 递归的合并两个有序链表
  ListNode mergeTwo(ListNode a, ListNode b){
      ListNode head = null;
      if(a==null)return b;
      if(b==null)return a;
      if(a.val<=b.val){
          a.next=mergeTwo(a.next,b);
          head=a;
      }else{
          b.next=mergeTwo(b.next,a);
          head=b;
      }
      return head;
  }
  • 类似的思路们也是用递归实现归并排序,但是这里区间的划分用了不同的处理,我们在取到中点后直接在中点处断开,将一个链表真正分成两个链表,这两个链表也就是左右链表,此时也就不存在中点了,可以直接调用主函数得到排序后的链表去合并,因为入参和主函数一致了都为头节点
java 复制代码
  public ListNode sortList(ListNode head) {
      if(head==null || head.next==null)return head;
      ListNode slow = head, fast = head.next;
      while(fast!=null && fast.next!=null){
          slow = slow.next;
          fast = fast.next.next;
      }
      // head 和 temp 相当于左右两个链表各自的头节点
      ListNode temp = slow.next;
      slow.next=null;
      // 调用自己得到排序后的链表
      ListNode left = sortList(head);
      ListNode right = sortList(temp);
      // 合并两个有序链表
      return mergeTwo(left,right);
  }
  // 非递归的合并两个有序链表
  public ListNode mergeTwo(ListNode a, ListNode b){
      ListNode res = new ListNode(0);
      ListNode head = res;
      while(a!=null && b!=null){
          if(a.val<=b.val){
              head.next=a;
              a=a.next;
          }else{
              head.next=b;
              b=b.next;
          }
          head=head.next;
      }
      head.next=a==null?b:a;
      return res.next;
  }
  • 以上两种解法由于都是使用递归的解法,所以空间复杂度都为 O(logn),想要空间复杂度为 O(1),可以采用自底向上的非递归解法,原文。

  • 例如 4,3,1,7,8,9,2,11,5,6,在递归解法中我们递归到最底层会得到两个长度为 1 的链表(只有一个节点的链表必定有序)然后合并成长度为 2 的有序链表,再往上回溯,两两合并得到长度为 4 的有序链表...所以我们的思路就是先两个两个地 merge ,再四个四个地 merge...

  • 例如 [4,3,1,7,8,9,2,11,5,6]

    复制代码
    step=1: (3->4)->(1->7)->(8->9)->(2->11)->(5->6)
    step=2: (1->3->4->7)->(2->8->9->11)->(5->6)
    step=4: (1->2->3->4->7->8->9->11)->5->6
    step=8: (1->2->3->4->5->6->7->8->9->11)
  • 这里需要涉及到的其实就是两个操作,先切割(cut)出两个链表,再合并(merge)两个链表,cut 指的是比如最开始我们需要两个两个 merge,假设链表为 3->2->1->4->5->null,我们先切割出两个长度为 1 的链表 3->null2->null,剩下 1->4->5->null。merge 就比如将以上切割出的两个链表合并为 2->3->null,所以我们可以先写出伪代码如下

java 复制代码
  ListNode cur = dummy.next;
  LIstNode tail = dummy;
  for(int step = 1; step < length; step *= 2){
  	  while(cur != null){
  		   ListNode left = cur;//left:3->2->1->4->5->null
  		   ListNode right = cut(left,step);//left:3->null,right:2->1->4->5->null
  		   cur = cut(right,step);//left:3->null,right:2->null,cur:1->4->5->null
  		   tail.next = merge(left,right);// 合并后的链表一个个添加到尾部
  		   while(tail.next != null)tail = tail.next;//保持 tail 始终为尾部
  	  }
  }
  • 最终代码如下
java 复制代码
  public ListNode sortList(ListNode head) {
  	  ListNode dummy = new ListNode(0);
      dummy.next = head;
      int length = 0;
      ListNode temp = head;
      // 计算链表长度
      while(temp != null){
          temp=temp.next;
          ++length;
      }
      // 用 size 更准确,每次切出 size 个节点
      for(int size = 1; size < length; size *= 2){ 
      	  // cur 只用作遍历前一个新链表,最开始从 head 开始,所以最上面 dummy.next = head;
          ListNode cur = dummy.next;
          // tail 用来获取下一个新链表
          ListNode tail = dummy;
          while(cur != null){
              ListNode left = cur;
              ListNode right = cut(left, size);
              cur = cut(right, size);
              tail.next = mergeTwo(left, right);
              while(tail.next != null)tail = tail.next;
          }
      }
      return dummy.next;
  }
  ListNode cut(ListNode head,int n){
      ListNode p = head;
      // 注意只能走 n-1 步,比如要切出一个节点就只能走 0 步才能把第一个节点切出来
      while (p != null && --n > 0)p = p.next;
      // 不够切只能返回 null 了
      if(p==null)return null;
      ListNode next = p.next;
      // 断开形成新链表
      p.next = null;
      // 返回切掉 n 个节点后的新链表的头节点
      return next;
  }
  public ListNode mergeTwo(ListNode a, ListNode b){
      ListNode res = new ListNode(0);
      ListNode head = res;
      while(a!=null && b!=null){
          if(a.val<=b.val){
              head.next=a;
              a=a.next;
          }else{
              head.next=b;
              b=b.next;
          }
          head=head.next;
      }
      head.next=a==null?b:a;
      return res.next;
  }
相关推荐
秋9几秒前
Go语言(Golang)开发工程师全景解析:岗位职责·语言优势与使用场景·各城市薪资·发展前景·高考志愿填报(2026版)
开发语言·golang·高考
huangdong_1 小时前
1688商品图片采集技术解析:登录态处理与SKU图自动分类
开发语言
youngerwang1 小时前
【从搬运工到协处理器:网卡芯片架构、算法、验证与边缘演进深度剖析】
网络·算法·架构·芯片
chase_my_dream1 小时前
C++ + SLAM 高频面试问题整理
开发语言·c++·面试
KaMeidebaby1 小时前
卡梅德生物技术快报|纯化重组蛋白实操详解
人工智能·python·tcp/ip·算法·机器学习
Cloud_Shy6181 小时前
解读《Effective Python 3rd Edition》:从练气到老魔(第五章 Item 30 - 32)
开发语言·人工智能·笔记·python·学习方法
天佑木枫2 小时前
15天Python入门系列 · 序
开发语言·python
手写码匠2 小时前
从零实现 Prompt 工程引擎:结构化提示、自动优化与多轮自省体系
人工智能·深度学习·算法·aigc
无限码力3 小时前
阿里算法岗 0530笔试真题 - 多约束条件下的元素匹配统计
算法·阿里笔试真题·阿里机试真题·阿里算法岗笔试
lqqjuly3 小时前
MLA — 多头潜在注意力深度解析
深度学习·神经网络·算法