链表的排序

165 阅读2分钟

一次面试中碰到了这个题,面试官要求用快排,我第一时间想到的就是快慢指针,因为这种方法可以让链表的结构不被破坏,但面试官要我用别的思路来解决,在面试官的提示下勉勉强强想到了另一种思路,但那段时间真的很疲惫,每天4个小时实习上下班的地铁+周末去学校给老师打工,还连续两个月,身心俱疲。
表达能力差到极点,说了一句这种思路和归并感觉一样了,但我的本意不是算法上的相同,而是说和归并在链表排序上一样需要大幅度破坏原本链表的结构,这让面试官觉得我分不清快排和归并,面试到这个时候我就已经状态不对了,长期得疲惫加此时的失落感,走到了最后一轮技术三面了,还听说前面的评价还都不错,然后成这样,算时间复杂度的时候就已经心不在焉了,居然说成了log(n),最终理所当然的GG,这篇文章用来总结一下这道题目,同时引入几道他的变式,也提醒大家不要太累了,哪怕还年轻,好在今天把学校的事搞得差不多了,不然真的折磨,好好歇两天,不要下次面试又阿巴阿巴。

题目:链表排序

这是一道经典题目,nlog(n)的时间复杂度下,快排、归并、堆排是都可以完成的。
先来看归并的解法,一共是两种自顶向下的递归和自底向上的迭代

归并的解法

自顶向下————递归

class Solution {
    public ListNode sortList(ListNode head) {
        return gSort(head);
    }
    public ListNode gSort(ListNode head) {
        if(head == null || head.next == null) {
            return head;
        }
        ListNode fast = head.next;
        ListNode slow = head;
        
        while(fast != null && fast.next != null) {
            fast = fast.next.next;
            slow = slow.next;
        }
        ListNode temp = slow.next;
        slow.next = null;
        ListNode left = gSort(head);
        ListNode right = gSort(temp);
        return merge(left, right);
    }
    public ListNode merge(ListNode left, ListNode right) {
        ListNode temp = new ListNode(0);
        ListNode res = temp;
        while(left != null && right != null) {
            if(left.val <= right.val) {
                temp.next = left;
                left = left.next;
            } else {
                temp.next = right;
                right = right.next;
            }
            temp = temp.next;
        }
        temp.next = left != null ? left : right;
        return res.next;
    }
}

时间复杂度:O(nlog n),其中 n 是链表的长度。
空间复杂度:O(logn),其中 n 是链表的长度。空间复杂度主要取决于递归调用的栈空间。

自底向上————迭代

class Solution {
    public ListNode sortList(ListNode head) {
        if (head == null) {
            return head;
        }
        int length = 0;
        ListNode node = head;
        while (node != null) {
            length++;
            node = node.next;
        }
        ListNode dummyHead = new ListNode(0, head);
        for (int subLength = 1; subLength < length; subLength *= 1) {
            ListNode prev = dummyHead, curr = dummyHead.next;
            while (curr != null) {
                ListNode head1 = curr;
                for (int i = 1; i < subLength && curr.next != null; i++) {
                    curr = curr.next;
                }
                ListNode head2 = curr.next;
                curr.next = null;
                curr = head2;
                for (int i = 1; i < subLength && curr != null && curr.next != null; i++) {
                    curr = curr.next;
                }
                ListNode next = null;
                if (curr != null) {
                    next = curr.next;
                    curr.next = null;
                }
                ListNode merged = merge(head1, head2);
                prev.next = merged;
                while (prev.next != null) {
                    prev = prev.next;
                }
                curr = next;
            }
        }
        return dummyHead.next;
    }
    public ListNode merge(ListNode left, ListNode right) {
        ListNode temp = new ListNode(0);
        ListNode res = temp;
        while(left != null && right != null) {
            if(left.val <= right.val) {
                temp.next = left;
                left = left.next;
            } else {
                temp.next = right;
                right = right.next;
            }
            temp = temp.next;
        }
        temp.next = left != null ? left : right;
        return res.next;
    }
}

时间复杂度:O(nlogn),其中 n 是链表的长度。
空间复杂度:O(1)。

快排的解法

这个是重头戏,也是面试里让我和面试官没有达成共识的地方,这里放两种思路,一种是我一开始的思路,一种是面试官引导出的思路,至于面试官的思路,我反问请教的时候他不愿意讲。

快慢指针链表快排

class Solution {
    public ListNode sortList(ListNode head) {
        //采用快速排序
        quickSort(head, null);
        return head;
    }
    public  void quickSort(ListNode head, ListNode end) {
        if (head != end) {
            ListNode node = partion(head, end);
            quickSort(head, node);
            quickSort(node.next, end);
        }
    }

    public  ListNode partion(ListNode head, ListNode end) {
        ListNode p1 = head, p2 = head.next;

        //走到末尾才停
        while (p2 != end) {

            //大于key值时,p1向前走一步,交换p1与p2的值
            if (p2.val < head.val) {
                p1 = p1.next;

                int temp = p1.val;
                p1.val = p2.val;
                p2.val = temp;
            }
            p2 = p2.next;
        }

        //当有序时,不交换p1和key值
        if (p1 != head) {
            int temp = p1.val;
            p1.val = head.val;
            head.val = temp;
        }
        return p1;
    }
}

荷兰国旗链表快排

class Solution {
    public ListNode sortList(ListNode head) {
        return quickSort(head);
    }
    
    public ListNode quickSort(ListNode head) {
        if(head == null || head.next == null) return head;
        ListNode slow = head, fast = head;
        while(fast.next != null && fast.next.next != null){
            fast = fast.next.next;
            slow = slow.next;
        }
        int val = slow.val;
        ListNode h1 = new ListNode();
        ListNode h2 = new ListNode();
        ListNode h3 = new ListNode();
        ListNode t1 = h1, t2 = h2, t3 = h3, cur = head;
        while(cur != null) {
            ListNode next = cur.next;
            if(cur.val < val) {
                cur.next = t1.next;
                t1.next = cur;
                t1 = t1.next;
            } else if(cur.val > val) {
                cur.next = t2.next;
                t2.next = cur;
                t2 = t2.next;
            } else {
                cur.next = t3.next;
                t3.next = cur;
                t3 = t3.next;
            }
            cur = next;
        }

        h1 = quickSort(h1.next);
        h2 = quickSort(h2.next);
        h3 = h3.next;
        t3.next = h2;
        if(h1 == null) {
            return h3;
        } else {
            t1 = h1;
            while(t1.next != null) {
                t1 = t1.next;
            }
            t1.next = h3;
            return h1;
        }
    }
}

变式:链表排序之多个有序链表排序

这个变式是一道天然的归并题,多个有序链表可以看作进行单链表排序时,使用了归并的方法到了一定程度,归并是这道题最自然而然的解法。

class Solution {
    public ListNode mergeKLists(ListNode[] lists) {
        if(lists.length == 0){
            return null;
        }
        return mergeList(lists, 0, lists.length-1);
    }
    ListNode mergeList(ListNode[] lists, int left, int right){
        if(left == right){
            return lists[left];
        }
        int mid = (left + right) / 2;
        ListNode leftNode = mergeList(lists, left, mid);
        ListNode rightNode = mergeList(lists, mid+1, right);
        return merge(leftNode, rightNode);
    }
    ListNode merge(ListNode leftNode, ListNode rightNode) {
        ListNode temp = new ListNode(0);
        ListNode cur = temp;
        while(leftNode != null && rightNode != null) {
            if(leftNode.val <= rightNode.val) {
                cur.next = leftNode;
                leftNode = leftNode.next;
            } else{
                cur.next = rightNode;
                rightNode = rightNode.next;
            }
            cur = cur.next;
        }
        if(leftNode != null) {
            cur.next = leftNode;
        }
        if(rightNode != null) {
            cur.next = rightNode;
        }
        return temp.next;
    }
}

时间复杂度:O(kn×logk)
空间复杂度:O(logk)