• leetcode: sortlist之四种方法


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

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

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

      1 /**
      2  * Definition for partition method result value
      3  *
      4  */
      5 class PartitionResult {
      6     ListNode head;
      7     ListNode tail;
      8     ListNode pre_pivot;
      9     ListNode pivot_node;
     10     
     11     public PartitionResult(ListNode head, ListNode tail) {
     12         // TODO Auto-generated constructor stub
     13         this.head = head;
     14         this.tail = tail;
     15         pre_pivot = null;
     16         pivot_node = null;
     17     }
     18 }
     19 
     20 public class Solution {
     21     ListNode head = null;
     22     
     23     private void swap(ListNode prei, ListNode i, ListNode prej, ListNode j) {
     24         if (i != prej) { //i isn't adjacent to j
     25             if (prei != null) //prei == null means i is the list's head
     26                 prei.next = j;
     27             
     28             ListNode cpy = j.next;
     29             j.next = i.next;
     30             
     31             prej.next = i;
     32             i.next = cpy; 
     33         }
     34         else { //i adjacent to j means i == prej
     35             if (prei != null) //prei == null means i is the list's head
     36                 prei.next = j;
     37             
     38             ListNode cpy = j.next;
     39             j.next = i;
     40             i.next = cpy;
     41         }
     42     }
     43     
     44     /**
     45      * partition [p, r] inplace and return [head, tail, pre_pivot, pivot_node]
     46      * 
     47      * @param preP
     48      * @param p
     49      * @param r
     50      * @param nextR
     51      * @return
     52      */
     53     private PartitionResult partition(ListNode prep, ListNode p, ListNode r) {
     54         int pivot_element = r.val;
     55         
     56         PartitionResult partitionResult = new PartitionResult(p, r);
     57         
     58         ListNode i = prep;
     59         ListNode prei = null;
     60         ListNode prej = prep;
     61        
     62         for (ListNode j = p; j != r; prej = j, j = j.next) {
     63             if (j.val <= pivot_element) {
     64                 
     65                 prei = i;
     66                 
     67                 //++i
     68                 if (i != null) {
     69                     i = i.next;
     70                 }
     71                 else {
     72                     i = partitionResult.head;
     73                     partitionResult.head = j; //modify cur head
     74                     
     75                     if (this.head == i)
     76                         this.head = j;
     77                 }
     78 
     79                 //swap i node and j node
     80                 if (i != j) {
     81                     swap(prei, i, prej, j);
     82                     
     83                     //swap i and j reference
     84                     ListNode cpy = i;
     85                     i = j;
     86                     j = cpy;
     87                 }
     88             }
     89         }
     90         
     91         //swap i + 1 node and r node
     92         if (i != null) {
     93             prei = i;
     94             i = i.next;
     95         }
     96         else {
     97             i = partitionResult.head;
     98             partitionResult.head = r;
     99             
    100             if (this.head == i)
    101                 this.head = r;
    102         }
    103         
    104         swap(prei, i, prej, r);
    105         
    106         ListNode cpy = i;
    107         i = r;
    108         r = cpy;
    109         
    110         //modify tail
    111         partitionResult.tail = i;
    112         
    113         //set new pre pivot node and pivot node
    114         partitionResult.pre_pivot = prej;
    115         partitionResult.pivot_node = i; 
    116                 
    117         return partitionResult;
    118     }
    119     
    120     
    121     /**
    122      * single linked list quickSort [head, tail]
    123      * @param head
    124      * @param tail
    125      * @return
    126      */
    127     private void quickSort(ListNode preHead, ListNode head, ListNode tail) {        
    128         if (head != null && tail != null && head != tail) {
    129             PartitionResult partitionResult = partition(preHead, head, tail);
    130             
    131             quickSort(preHead, partitionResult.head, partitionResult.pre_pivot);
    132             
    133             if (partitionResult.pivot_node != partitionResult.tail)
    134                 quickSort(partitionResult.pivot_node, partitionResult.pivot_node.next, partitionResult.tail);
    135         }
    136     }
    137     
    138     public ListNode sortList(ListNode head) {
    139         this.head = head;
    140         ListNode tail = null;
    141         
    142         for (ListNode itr = head; itr != null; tail = itr, itr = itr.next);
    143         
    144         quickSort(null, head, tail);
    145         
    146         return head;
    147     }
    148 }
    View Code

    方法一的缺点很明显:复杂容易出错,没有利用链表的优势。数组快排交换节点的本质,是使得在左边元素<=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);
        }
    }
    View Code

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

     1 public class Solution {
     2     /**
     3      * core idea is the link not swap
     4      * head != null
     5      * and use the head node as a pivot node
     6      * 
     7      * [head, tail)
     8      * 
     9      * @param head
    10      * @param tail
    11      * @return [leftPartHead, leftPartEndNode]
    12      */
    13     private ListNode[] partition(ListNode head, ListNode tail) {
    14         //cal avg as the pivot value
    15         int sum = 0, count = 0;
    16         
    17         for (ListNode itr = head; itr != tail; itr = itr.next) {
    18             sum += itr.val;
    19             ++count;
    20         }
    21         
    22         float x = (float)sum / count; //notice if int x will lead to infinite loop (for example -39 -38)
    23         
    24         boolean same = true;
    25         
    26         //l1 <= x
    27         //l2 > x        
    28         ListNode l1Head = new ListNode(-1), l1Itr = l1Head;
    29         ListNode l2Head = new ListNode(-1), l2Itr = l2Head;
    30         
    31         for (ListNode itr = head, pre = head; itr != tail; pre = itr, itr = itr.next) {
    32             if (itr.val != pre.val) {
    33                 same = false;
    34             }
    35             
    36             if (itr.val < x) {
    37                 l1Itr.next = itr;
    38                 l1Itr = itr;
    39             }
    40             else {
    41                 l2Itr.next = itr;
    42                 l2Itr = itr;
    43             }
    44         }
    45         
    46         ListNode [] listNodes = new ListNode[2];
    47         
    48         listNodes[0] = l1Head.next;
    49         
    50         if (!same) {
    51             //l1->l2->tail
    52             l2Itr.next = tail; //if l2Head == l2Itr
    53             l1Itr.next = l2Head.next;
    54                     
    55             listNodes[1] = l1Itr;
    56         }
    57         else {
    58             listNodes[1] = l1Head.next;
    59         }
    60 
    61         //useless node set to null
    62         l1Head = null;
    63         l2Head = null;
    64         
    65         return listNodes;
    66     }
    67     
    68     //quick sort for list
    69     private ListNode quickSort(ListNode head, ListNode tail) {
    70         ListNode curHead = head;
    71         
    72         if (head != tail && head.next != tail) {
    73             ListNode [] rel = partition(head, tail); //after partition head node play a pivot role
    74             
    75             if (rel[0] != null) { //when rel[0] means that remain element is the same
    76                 curHead = quickSort(rel[0], rel[1].next); //maintain head node
    77                 
    78                 rel[1].next = quickSort(rel[1].next, tail); //link the two parts
    79             }
    80         }
    81         
    82         return curHead;
    83     }
    84     
    85     public ListNode sortList(ListNode head) {
    86         return quickSort(head, null);
    87     }
    88 }
    View Code

    方法三的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,偶数节点为中间节点右边那个节点。

     1 public class Solution {
     2     /**
     3      * merge l1 and l2 list
     4      * 
     5      * @param l1
     6      * @param l2
     7      * @return
     8      */
     9     private ListNode merge(ListNode l1, ListNode l2) {
    10         ListNode head = new ListNode(-1), itr = head;
    11         
    12         while (l1 != null && l2 != null) {
    13             if (l1.val < l2.val) {
    14                 itr.next = l1;
    15                 itr = l1;
    16                 l1 = l1.next;
    17             }
    18             else {
    19                 itr.next = l2;
    20                 itr = l2;
    21                 l2 = l2.next;
    22             }
    23         }
    24         
    25         //deal l1 or l2 remain element
    26         ListNode remail = null;
    27         
    28         if (l1 == null && l2 != null) {
    29             remail = l2;
    30         }
    31         else if (l2 == null && l1 != null) {
    32             remail = l1;
    33         }
    34         
    35         itr.next = remail;
    36         
    37         ListNode relHead = head.next;
    38         head = null;
    39         
    40         return relHead;
    41     }
    42     
    43     private ListNode mergeSort(ListNode head, ListNode tail) {
    44         if (head.next == tail) { //single node
    45             head.next = null;
    46             return head;
    47         }
    48             
    49         //locate the middle node
    50         //2 * mid = len
    51         //itr += 2; mid += 1;
    52         //notice that itr and mid start from the first node so it is not a exact middle location
    53         //actually it is the middle location + 1
    54         ListNode itr = head, mid = head;
    55         while (itr != tail) {
    56             itr = itr.next;
    57             
    58             if (itr != tail) {
    59                 itr = itr.next;
    60             }
    61             
    62             mid = mid.next;
    63         }
    64         
    65         ListNode l1 = mergeSort(head, mid);
    66         
    67         ListNode l2 = mergeSort(mid, tail);
    68         
    69         return merge(l1, l2);
    70     }
    71 
    72     public ListNode sortList(ListNode head) {
    73         if (head == null) //trap
    74             return null;
    75         
    76         return mergeSort(head, null);
    77     }
    78 }
    View Code

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

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

  • 相关阅读:
    java线程
    windows Server 安装
    nginx正则反向代理
    crontab定时任务启动脚本失败
    数据结构
    异常概念
    shell日期遍历复制文件
    多态和抽象
    图1 列出连通集
    PTA 代码注意事项
  • 原文地址:https://www.cnblogs.com/hzhesi/p/4058749.html
Copyright © 2020-2023  润新知