原题链接:https://oj.leetcode.com/problems/sort-list/

题目:空间复杂度为常数,时间复杂度为O(nlogn)的排序链表实现

方法一:第一想法是模拟数组的快速排序,参考了算法导论,于是思路被牵到了如何处理交换节点上,几经波折总算实现,不过提交的结果TLE。

 /**
* Definition for partition method result value
*
*/
class PartitionResult {
ListNode head;
ListNode tail;
ListNode pre_pivot;
ListNode pivot_node; public PartitionResult(ListNode head, ListNode tail) {
// TODO Auto-generated constructor stub
this.head = head;
this.tail = tail;
pre_pivot = null;
pivot_node = null;
}
} public class Solution {
ListNode head = null; private void swap(ListNode prei, ListNode i, ListNode prej, ListNode j) {
if (i != prej) { //i isn't adjacent to j
if (prei != null) //prei == null means i is the list's head
prei.next = j; ListNode cpy = j.next;
j.next = i.next; prej.next = i;
i.next = cpy;
}
else { //i adjacent to j means i == prej
if (prei != null) //prei == null means i is the list's head
prei.next = j; ListNode cpy = j.next;
j.next = i;
i.next = cpy;
}
} /**
* partition [p, r] inplace and return [head, tail, pre_pivot, pivot_node]
*
* @param preP
* @param p
* @param r
* @param nextR
* @return
*/
private PartitionResult partition(ListNode prep, ListNode p, ListNode r) {
int pivot_element = r.val; PartitionResult partitionResult = new PartitionResult(p, r); ListNode i = prep;
ListNode prei = null;
ListNode prej = prep; for (ListNode j = p; j != r; prej = j, j = j.next) {
if (j.val <= pivot_element) { prei = i; //++i
if (i != null) {
i = i.next;
}
else {
i = partitionResult.head;
partitionResult.head = j; //modify cur head if (this.head == i)
this.head = j;
} //swap i node and j node
if (i != j) {
swap(prei, i, prej, j); //swap i and j reference
ListNode cpy = i;
i = j;
j = cpy;
}
}
} //swap i + 1 node and r node
if (i != null) {
prei = i;
i = i.next;
}
else {
i = partitionResult.head;
partitionResult.head = r; if (this.head == i)
this.head = r;
} swap(prei, i, prej, r); ListNode cpy = i;
i = r;
r = cpy; //modify tail
partitionResult.tail = i; //set new pre pivot node and pivot node
partitionResult.pre_pivot = prej;
partitionResult.pivot_node = i; return partitionResult;
} /**
* single linked list quickSort [head, tail]
* @param head
* @param tail
* @return
*/
private void quickSort(ListNode preHead, ListNode head, ListNode tail) {
if (head != null && tail != null && head != tail) {
PartitionResult partitionResult = partition(preHead, head, tail); quickSort(preHead, partitionResult.head, partitionResult.pre_pivot); if (partitionResult.pivot_node != partitionResult.tail)
quickSort(partitionResult.pivot_node, partitionResult.pivot_node.next, partitionResult.tail);
}
} public ListNode sortList(ListNode head) {
this.head = head;
ListNode tail = null; for (ListNode itr = head; itr != null; tail = itr, itr = itr.next); quickSort(null, head, tail); return head;
}
}

方法一的缺点很明显:复杂容易出错,没有利用链表的优势。数组快排交换节点的本质,是使得在左边元素<=Pivot元素<=右边元素,因此在对链表进行快排时,可以构造左链表(l1),右链表(l2),及Pivot元素(x),使得l1 <= x < l2,再将l1 -> x -> l2相连,由此得到方法二。方法二的代码量较之方法一减少一半,无奈提交的结果仍然是TLE,错误的case与方法一一致,都是一个超长的输入。

public class Solution {
/**
* core idea is the link not swap
* head != null
* and use the head node as a pivot node
*
* [head, tail)
*
* @param head
* @param tail
* @return current head node
*/
private ListNode partition(ListNode head, ListNode tail) {
int x = head.val; //l1 <= x
//l2 > x
ListNode l1Head = new ListNode(-1), l1Itr = l1Head;
ListNode l2Head = new ListNode(-1), l2Itr = l2Head; for (ListNode itr = head.next; itr != tail; itr = itr.next) {
if (itr.val <= x) {
l1Itr.next = itr;
l1Itr = itr;
}
else {
l2Itr.next = itr;
l2Itr = itr;
}
} //l1->x->l2->tail
l1Itr.next = head;
l2Itr.next = tail; //if l2Head == l2Itr
head.next = l2Head.next; //useless node set to null
ListNode relHead = l1Head.next;
l1Head = null;
l2Head = null; return relHead;
} //quick sort for list
private ListNode quickSort(ListNode head, ListNode tail) {
ListNode curHead = head; if (head != tail) {
curHead = partition(head, tail); //after partition head node play a pivot role curHead = quickSort(curHead, head); //maintain head node head.next = quickSort(head.next, tail); //link two parts
} return curHead;
} public ListNode sortList(ListNode head) {
return quickSort(head, null);
}
}

影响快排性能的一个重要因素,就是Pivot元素的选取。方法二简单的使用了链表中的第一个节点作为Pivot元素,并不能保证很好的平均性能。参考了Discuss中一位网友取链表均值作为Pivot值的思路,实现了方法三。注意,之所以说是Pivot值,是因为与方法一,方法二在链表中选取Pivot元素不同,该Pivot值可能不在链表中。

 public class Solution {
/**
* core idea is the link not swap
* head != null
* and use the head node as a pivot node
*
* [head, tail)
*
* @param head
* @param tail
* @return [leftPartHead, leftPartEndNode]
*/
private ListNode[] partition(ListNode head, ListNode tail) {
//cal avg as the pivot value
int sum = 0, count = 0; for (ListNode itr = head; itr != tail; itr = itr.next) {
sum += itr.val;
++count;
} float x = (float)sum / count; //notice if int x will lead to infinite loop (for example -39 -38) boolean same = true; //l1 <= x
//l2 > x
ListNode l1Head = new ListNode(-1), l1Itr = l1Head;
ListNode l2Head = new ListNode(-1), l2Itr = l2Head; for (ListNode itr = head, pre = head; itr != tail; pre = itr, itr = itr.next) {
if (itr.val != pre.val) {
same = false;
} if (itr.val < x) {
l1Itr.next = itr;
l1Itr = itr;
}
else {
l2Itr.next = itr;
l2Itr = itr;
}
} ListNode [] listNodes = new ListNode[2]; listNodes[0] = l1Head.next; if (!same) {
//l1->l2->tail
l2Itr.next = tail; //if l2Head == l2Itr
l1Itr.next = l2Head.next; listNodes[1] = l1Itr;
}
else {
listNodes[1] = l1Head.next;
} //useless node set to null
l1Head = null;
l2Head = null; return listNodes;
} //quick sort for list
private ListNode quickSort(ListNode head, ListNode tail) {
ListNode curHead = head; if (head != tail && head.next != tail) {
ListNode [] rel = partition(head, tail); //after partition head node play a pivot role if (rel[0] != null) { //when rel[0] means that remain element is the same
curHead = quickSort(rel[0], rel[1].next); //maintain head node rel[1].next = quickSort(rel[1].next, tail); //link the two parts
}
} return curHead;
} public ListNode sortList(ListNode head) {
return quickSort(head, null);
}
}

方法三的trap:

1. 由于采用均值作为Pivot值,因此当链表中元素相等时,是没法继续划分的(当然也不需要继续划分,即可以结束),会造成无限循环

2. 题目中给的链表元素值为int型,如果均值为int,也会造成无法继续划分的情况,如{5, 6},均值为5,那么5, 6将被归为右链表,并且那么持续下去,造成无限循环

方法三提交结果总算AC啦(512ms),时间有波动。

方法四不再死磕链表快排,采用归并排序,对于此题来说,应该是比较直接合理的解决方案。值得注意的是怎么确定链表的中点(经典问题啦):中点意味着2*mid = length,因此可以设置两个引用,mid引用一次走一个节点,itr引用一次走两个节点,当itr到链表尾的时候,mid就在近似链表中间的位置了。之所以说是近似呢,是因为在我的代码中,mid和itr都是从链表中的第一个节点开始遍历的,因此length相当于-1了,对于奇数节点来说,mid为实际中间节点+1,偶数节点为中间节点右边那个节点。

 public class Solution {
/**
* merge l1 and l2 list
*
* @param l1
* @param l2
* @return
*/
private ListNode merge(ListNode l1, ListNode l2) {
ListNode head = new ListNode(-1), itr = head; while (l1 != null && l2 != null) {
if (l1.val < l2.val) {
itr.next = l1;
itr = l1;
l1 = l1.next;
}
else {
itr.next = l2;
itr = l2;
l2 = l2.next;
}
} //deal l1 or l2 remain element
ListNode remail = null; if (l1 == null && l2 != null) {
remail = l2;
}
else if (l2 == null && l1 != null) {
remail = l1;
} itr.next = remail; ListNode relHead = head.next;
head = null; return relHead;
} private ListNode mergeSort(ListNode head, ListNode tail) {
if (head.next == tail) { //single node
head.next = null;
return head;
} //locate the middle node
//2 * mid = len
//itr += 2; mid += 1;
//notice that itr and mid start from the first node so it is not a exact middle location
//actually it is the middle location + 1
ListNode itr = head, mid = head;
while (itr != tail) {
itr = itr.next; if (itr != tail) {
itr = itr.next;
} mid = mid.next;
} ListNode l1 = mergeSort(head, mid); ListNode l2 = mergeSort(mid, tail); return merge(l1, l2);
} public ListNode sortList(ListNode head) {
if (head == null) //trap
return null; return mergeSort(head, null);
}
}

方法四提交结果AC(500ms),时间有波动。

github地址:https://github.com/zrss/leetcode/tree/master/src/com/zrss/leetcode

leetcode: sortlist之四种方法的更多相关文章

  1. [Leetcode]315.计算右侧小于当前元素的个数 (6种方法)

    链接 给定一个整数数组 nums,按要求返回一个新数组 counts.数组 counts 有该性质: counts[i] 的值是  nums[i] 右侧小于 nums[i] 的元素的数量. 示例: 输 ...

  2. LeetCode OJ 143. Reorder List(两种方法,快慢指针,堆栈)

    Given a singly linked list L: L0→L1→…→Ln-1→Ln,reorder it to: L0→Ln→L1→Ln-1→L2→Ln-2→… You must do thi ...

  3. 记忆化搜索模板题---leetcode 1155. 掷骰子的N种方法

    1155. 掷骰子的N种方法 这里有 d 个一样的骰子,每个骰子上都有 f 个面,分别标号为 1, 2, ..., f. 我们约定:掷骰子的得到总点数为各骰子面朝上的数字的总和. 如果需要掷出的总点数 ...

  4. 三种方法获取Class对象的区别

    有关反射的内容见 java反射 得到某个类的Class对象有三种方法: 使用“类名.class”取得 Class.forName(String className) 通过该类实例对象的getClass ...

  5. 服务器文档下载zip格式 SQL Server SQL分页查询 C#过滤html标签 EF 延时加载与死锁 在JS方法中返回多个值的三种方法(转载) IEnumerable,ICollection,IList接口问题 不吹不擂,你想要的Python面试都在这里了【315+道题】 基于mvc三层架构和ajax技术实现最简单的文件上传 事件管理

    服务器文档下载zip格式   刚好这次项目中遇到了这个东西,就来弄一下,挺简单的,但是前台调用的时候弄错了,浪费了大半天的时间,本人也是菜鸟一枚.开始吧.(MVC的) @using Rattan.Co ...

  6. Maximal Rectangle [leetcode] 的三种思路

    第一种方法是利用DP.时间复杂度是 O(m * m * n) dp(i,j):矩阵中同一行以(i,j)结尾的所有为1的最长子串长度 代码例如以下: int maximalRectangle(vecto ...

  7. JS 判断数据类型的三种方法

    说到数据类型,我们先理一下JavaScript中常见的几种数据类型: 基本类型:string,number,boolean 特殊类型:undefined,null 引用类型:Object,Functi ...

  8. DataTable 转换成 Json的3种方法

    在web开发中,我们可能会有这样的需求,为了便于前台的JS的处理,我们需要将查询出的数据源格式比如:List<T>.DataTable转换为Json格式.特别在使用Extjs框架的时候,A ...

  9. Android之数据存储的五种方法

    1.Android数据存储的五种方法 (1)SharedPreferences数据存储 详情介绍:http://www.cnblogs.com/zhangmiao14/p/6201900.html 优 ...

随机推荐

  1. BZOJ 3514 (动态树)

    这两天终于基本理解了Link-Cut Tree这种神一般的东西.然后就来做这道题了. 原题是CodeChef上的.CodeChef上没有强制在线,且时限更宽松,所以似乎用莫队一样的算法把询问分组就能水 ...

  2. ie浏览器中 overflow:hidden无作用的解决方案

    原因: overflow:hidden失效 当父元素的直接子元素或者下级子元素的样式拥有position:relative属性时,父元素的overflow:hidden属性就会失效. 我在ie内发现子 ...

  3. Oracle 常用语句汇总

    1.查询当前用户的建表SQL: SELECT DBMS_METADATA.GET_DDL('TABLE','COL_MERCH_INFO') FROM DUAL; 2.查询当前用户的所有表: SELE ...

  4. Mapreduce运行过程分析(基于Hadoop2.4)——(一)

    1 概述 该瞅瞅MapReduce的内部执行原理了,曾经仅仅知道个皮毛,再不搞搞,不然怎么死的都不晓得.下文会以2.4版本号中的WordCount这个经典样例作为分析的切入点.一步步来看里面究竟是个什 ...

  5. 秒杀多线程第八篇 经典线程同步 信号量Semaphore

    阅读本篇之前推荐阅读以下姊妹篇: <秒杀多线程第四篇一个经典的多线程同步问题> <且不超过最大资源数量. 第三个參数能够用来传出先前的资源计数,设为NULL表示不须要传出. 注意:当 ...

  6. EJB开发第一个无状态会话bean、开发EJBclient

    开发第一个无状态会话bean EJB中的三中bean: 会话Bean(Session Bean) 负责与client交互,是编写业务逻辑的地方.在会话bean中能够通过JDBC直接操作数据库.但大多数 ...

  7. C# winform xml的增删改查

    代码如下: using System; using System.Collections.Generic; using System.IO; using System.Linq; using Syst ...

  8. Eclipse 添加快捷方式

    1.在/usr/share/applications创建一个desktop文件,命名为eclipse.desktop 文件内容如下 [Desktop Entry]Name=EclipseType=Ap ...

  9. Jdbc 事务

    package com.j1; import java.sql.Connection; import java.sql.SQLException; import com.mysql.jdbc.Prep ...

  10. hdu 1301

    最小生成树模板题 简单的prim算法 AC代码: #include <iostream> #include <stdio.h> #define INF 9999999 usin ...