原题链接: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. navicat 随笔提示的快捷键

    1.ctrl+q 打开查询窗口2.ctrl+/ 注释sql语句3.ctrl+shift +/ 解除注释4.ctrl+r 运行查询窗口的sql语句5.ctrl+shift+r 只运行选中的sql语句6. ...

  2. python遗传算法实现数据拟合

    python据说功能强大,触角伸到各个领域,网上搜了一下其科学计算和工程计算能力也相当强,具备各种第三方包,除了性能软肋外,其他无可指摘,甚至可以同matlab等专业工具一较高下. 从网上找了一个使用 ...

  3. js的意义,引用方法及变量

    一 JavaScript的意义. Javascript是浏览器端的脚本语言,它能够访问web页面的元素和运行它得浏览器,从而可以操作元素,创建新的元素等.它主要的作用有: 1.以指定尺寸.位置和样式( ...

  4. for 的多重循环--java

    for的多重循环--java 利用for的多重循环打印出四种不同的三角形的图案. 图案如下: 4种不同三角形图案打印如下------------------******---------------- ...

  5. 用SHELL与列表处理了件尴尬事

    与列表语法 command-1 && command-2 && command-3 && command-4 && ...command ...

  6. rac 10g 加入节点具体解释

    目标: 当前我环境中是有两个节点RAC1和RAC2 节点.如今添加一个RAC3节点.   概要:为现有的Oracle10g RAC 加入节点大致包含下面步骤: 1. 配置新的server节点上的硬件及 ...

  7. void*指针

    C++提供了一种特殊的指针类型void*,它可以保存任何类型对象的地址: void*表明该指针与一地址值相关,但不清楚存储在此地址上的对象的类型. void*指针只支持几种有限的操作: 1)与另一个指 ...

  8. VLV INDEX

    Normally, when the Directory Server conducts a search, the server looks through the entire entry for ...

  9. source insight3.5中文乱码解决方案

    source insight3.5中文乱码,网上看别人说改变宽字体.宋体等方法都不起效.根本原因是,source insight 3.5 不支持Unicode编码,所以导致中文的乱码,将文件转为gb2 ...

  10. SpringMVC学习简单HelloWorld实例

    首先还是从一个简单的Hello World项目说起: 我机器的开发环境为: Ubuntu12.04(不同操作系统对本系列项目没有影响): 开发工具:Eclipse For JavaEE: 数据库:My ...