LeetCode 148.排序链表

133 阅读1分钟

「这是我参与2022首次更文挑战的第42天,活动详情查看:2022首次更文挑战」。

题目:给定链表头节点head,要求按照升序返回排好之后的链表。

解题思路

题目要求在O(nlogn)O(nlogn)的时间复杂度完成排序,在排序算法中,时间复杂度在O(logn)O(logn)级别的只有二分法,而排序算法中含二分思想的主要是归并排序和快速排序,这两个时间复杂度都是O(nlogn)O(nlogn),并且归并排序是稳定的,快速排序是不稳定的。

本题采用归并排序进行求解,归并排序利用的是自顶向下的思想,将一个大问题分割为等效的子问题,通过对子问题的求解来得到整个问题的解,可得代码如下:

public ListNode sortList(ListNode head) {
        return sorted(head);
    }

    public ListNode sorted(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 right = sorted(slow.next);
        slow.next=null;
        ListNode left = sorted(head);
        return mergeList(left, right);
    }

    public ListNode mergeList(ListNode left, ListNode right){
        ListNode temp = new ListNode(-1);
        ListNode cur = temp;
        while(left!=null&&right!=null){
            if(left.val<right.val){
                cur.next = left;
                left = left.next;
            }else {
                cur.next = right;
                right = right.next;
            }
            cur = cur.next;
        }
        cur.next = left==null?right:left;
        return temp.next;
    }

上述代码需要注意的是,在sorted的归并里面,fast指向的并不是head,而是head.next,之所以这么设置是为了避免栈溢出。举个例子:假如当前list只有两个节点[3, 4],如果fastslow都指向头节点,则sorted会把链表划分为[3, 4]null两部分,之后[3, 4]再次递归,一直循环,最终会导致栈溢出。

当然除了这种解决方法,还有一种解决方法,就是让fastslow都还指向head,所不同的是寻找mid节点的判断条件要改为下面的:

public ListNode sorted(ListNode head){
        if(head==null||head.next==null) return head;
        ListNode fast = head;
        ListNode slow = head;
        while(fast.next!=null&&fast.next.next!=null){
            fast = fast.next.next;
            slow = slow.next;
        }
        ListNode right = sorted(slow.next);
        slow.next=null;
        ListNode left = sorted(head);
        return mergeList(left, right);
    }

这样做的目的也是解决当链表只剩两个节点避免一直递归的情况。

LeetCode 147和本题类似,只不过那题是要求对链表排序使用插入排序,插入排序思路很简单,无非就是将当前节点和已排好的末尾节点进行比较,如果大于则直接插入,如果小于则往前面找,直到找到合适的插入点,插入即可,插入排序难点在于对细节的把握,代码如下:

public ListNode insertionSortList(ListNode head) {
        if(head==null||head.next==null) return head;
        ListNode point = new ListNode(-1);
        point.next = head;
        ListNode lastNode = head;
        ListNode cur = head.next;
        while(cur!=null){
            if(cur.val>=lastNode.val){
                lastNode = lastNode.next;
            }else {
                ListNode temp = point;
                while(temp.next.val<cur.val){
                    temp = temp.next;
                }
                lastNode.next=cur.next;
                cur.next=temp.next;
                temp.next=cur;
            }
            cur = lastNode.next;
        }
        return point.next;
    }

上述代码时间复杂度为O(n2)O(n^2),空间复杂度为O(1)O(1)