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

 

你可能感兴趣的:(LeetCode)