Leetcode --- 链表常见问题5(链表排序)

简介: Leetcode --- 链表常见问题5(链表排序)

1.插入排序链表(147-中)



问题描述:插入排序基本思想是每一步将一个待排序的记录,插入到前面已经排好序的有序序列中去,直到插完所有元素为止。


案例:

输入:head = [4,2,1,3]
输出:[1,2,3,4]


思路:本题基本思路是插入排序,如何维护已排序链表,如何遍历剩余链表?两个关键问题:


  • lastSorted:维护已经排好序链表的最后一个节点
  • cur:遍历剩余链表待插入节点


注意:对于一般情况,我们需要从头遍历已经排好序的链表,找到该位置,然后重连。


代码:

public ListNode insertionSortList(ListNode head) {
    if (head == null) return head;
    ListNode dummy = new ListNode(0);
    dummy.next = head;
    ListNode lastSorted = head;  // 已经排好序链表的最后一个节点
    ListNode cur = head.next;   //待插入的节点
    while (cur != null) {
        if (lastSorted.val <= cur.val) {
            lastSorted = lastSorted.next;
        } else {
            ListNode pre = dummy;
            while (pre.next.val <= cur.val) {
                pre = pre.next;
            }
            lastSorted.next = cur.next; // 取出待插入节点
            cur.next = pre.next;
            pre.next = cur;
        }
        cur = lastSorted.next;
    } 
    return dummy.next;
}


注意:时间复杂度O(n^2)


2.归并排序链表(148-中)



问题描述:给你链表的头结点 head ,请将其按 升序 排列并返回 排序后的链表 。要求:O(n log n) 时间复杂度和常数级空间复杂度。


案例:

输入:head = [4,2,1,3]
输出:[1,2,3,4]


思路:根据要求这里使用归并排序进行求解。时间复杂度O(n log n) ,基本流程:


  • 快慢指针找到链表的中点
  • 对左右分区进行递归
  • 遍历链表,比较连接


代码:

public ListNode sortList(ListNode head) {
    if (head == null || head.next == null) return head;
    // 将两个链表平均分开
    ListNode slow = head, fast = head.next;
    while (fast != null && fast.next != null) {
        slow = slow.next;
        fast = fast.next.next;
    }
    ListNode tmp = slow.next;
    slow.next = null;
    // 拆分
    ListNode left = sortList(head);
    ListNode right = sortList(tmp);
    // 合并
    ListNode dummy = new ListNode(0);
    ListNode cur = dummy;
    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 ? left : right;
    return dummy.next;
}


3.快速排序链表



思路:快排实现思路,选取基准值遍历链表,比基准值小的进行头插,大于基准值的尾插:


  • lhead维护的是小于基准值的头插指针
  • utail维护的是大于等于基准值的尾插指针


然后对这两部分链表进行分治,保证区间不变性


  • [ lhead , head )是小于基准值的一部分
  • [ head.next , end )是大于等于基准值的一部分


代码:


public ListNode sortList(ListNode head) {
    return quickSort(head, null);
}
private ListNode quickSort(ListNode head, ListNode end) {
    if (head == end || head.next == end) return head;
    ListNode Ihead = head, utail = head, cur = head.next;
    while (cur != end) {
        ListNode next = cur.next;
        if (cur.val < head.val) {
            cur.next = Ihead;
            Ihead = cur;
        } else {
            utail.next = cur;
            utail = cur;
        }
        cur = next;
    }
    utail.next = end;
    ListNode node = quickSort(Ihead, head);
    head.next = quickSort(head.next, end);
    return node;
}


也可以设置三个虚拟节点,类似荷兰国旗(即小于等于或者大于给定值)对链表进行递归拆分,最后拼接。代码如下:

public ListNode sortList(ListNode head) {
    if (head == null || head.next == null) return head;
    ListNode lowDummy = new ListNode(0);
    ListNode low = lowDummy;
    ListNode equalDummy = new ListNode(0);
    ListNode equal = equalDummy;
    ListNode highDummy = new ListNode(0);
    ListNode high = highDummy;
    // 遍历链表,进行分区(partition)
    ListNode cur = head;
    while (cur != null) {
        if(cur.val < head.val) {
            low.next = cur;
            low = low.next;
        } else if (cur.val == head.val) {
            equal.next = cur;
            equal = equal.next;
        } else {
            high.next = cur;
            high = high.next;
        }
        cur = cur.next;
    }
    // 避免递归过程中成环
    low.next = null;
    high.next = null;
    lowDummy.next = sortList(lowDummy.next);
    highDummy.next = sortList(highDummy.next);
    // 连接三部分
    low = lowDummy;
    while (low.next != null) {
        low = low.next;
    }
    low.next = equalDummy.next;
    equal.next = highDummy.next;
    return lowDummy.next;
}


另外根据快排思想可以对值进行交换,但有些题目不允许这么做。

public ListNode sortList(ListNode head) {
    if (head == null || head.next == null) return head;
    quickSort(head, null);
    return head;
}
private void quickSort(ListNode begin, ListNode end) {
    if (begin != end && begin.next != end) {
        ListNode node = partition(begin, end);
        quickSort(begin, node);
        quickSort(node.next, end);
    }
}
private ListNode partition(ListNode head, ListNode end) {
    ListNode p1 = head, p2 = head.next;
    while (p2 != end) {
        if (p2.val < head.val) {
            p1 = p1.next;
            swap(p1, p2);
        }
        p2 = p2.next;
    }
    swap(p1, head);
    return p1;
}
private void swap(ListNode i, ListNode j) {
    int tmp = i.val;
    i.val = j.val;
    j.val = tmp;
}


注意:快排链表在Leetcode运行时间都比较长,有时会超时,不知道为什么,希望大佬们指教。


总之,排序链表首选归并排序,时间复杂度稳定在O(nlogn);如果用快速排序可以使用上述中的第一种,但由于单链表不能随意获取划分值pivot,时间复杂度可能退化为O(nlogn)。


4.排序奇升偶降链表(字节高频题)



问题描述:给定一个奇数位升序,偶数位降序的链表,将其重新排序。不能使用额外空间。


案例:

输入: 1->8->3->6->5->4->7->2->NULL
输出: 1->2->3->4->5->6->7->8->NULL


思路:核心主要分为以下三步:


  • 按照奇偶位置拆分链表(leetcode328)
  • 反转偶链表(leetcode206)
  • 合并两个有序链表(leetcode21)


ps:在拆分链表时遇到一个坑,一定要 odd.next = null, 否则出现死循环


代码:

public class Solution002 {
    public static class ListNode {
        int val;
        ListNode next;
        public ListNode() {
        }
        public ListNode(int val) {
            this.val = val;
        }
        public ListNode(int val, ListNode next) {
            this.val = val;
            this.next = next;
        }
    }
    public static void main(String[] args) {
        // 测试 1->4->3->2->NULL
        ListNode node = new ListNode(1);
        node.next = new ListNode(4);
        node.next.next = new ListNode(3);
        node.next.next.next = new ListNode(2);
        ListNode sortedNode = sort(node);
        while (sortedNode != null) {
            System.out.print(sortedNode.val + " ");
            sortedNode = sortedNode.next;
        }
    }
    public static ListNode sort(ListNode head) {
        // 1.根据奇偶数位拆分链表
        ListNode oddHead = split(head);
        // 2.反转偶数位链表
        ListNode newEvenHead = reverse(evenHead);
        // 3.拼接两个有序链表
        ListNode sortedNode = merge(oddHead, newEvenHead);
        return sortedNode;
    }
    public static ListNode evenHead;
    public static ListNode split(ListNode head) {
        ListNode odd = head, even = head.next;
        evenHead = even;
        while (even != null && even.next != null) {
            odd.next = odd.next.next;
            odd = odd.next;
            even.next = even.next.next;
            even = even.next;
        }
//        odd.next = evenHead;  t328:尾部连接
        odd.next = null;
        return head;
    }
    public static ListNode reverse(ListNode head) {
        ListNode pre = null, next;
        while (head != null) {
            next = head.next;
            head.next = pre;
            pre = head;
            head = next;
        }
        return pre;
    }
    public static ListNode merge(ListNode l1, ListNode l2) {
        ListNode pre = new ListNode(-1);
        ListNode cur = pre;
        while (l1 != null && l2 != null) {
            if (l1.val < l2.val) {
                cur.next = l1;
                l1 = l1.next;
            } else {
                cur.next = l2;
                l2 = l2.next;
            }
            cur = cur.next;
        }
        cur.next = (l1 == null) ? l2 : l1;
        return pre.next;
    }
}


相关文章
|
1月前
【力扣】-- 移除链表元素
【力扣】-- 移除链表元素
36 1
|
1月前
Leetcode第21题(合并两个有序链表)
这篇文章介绍了如何使用非递归和递归方法解决LeetCode第21题,即合并两个有序链表的问题。
50 0
Leetcode第21题(合并两个有序链表)
|
1月前
LeetCode第二十四题(两两交换链表中的节点)
这篇文章介绍了LeetCode第24题的解法,即如何通过使用三个指针(preNode, curNode, curNextNode)来两两交换链表中的节点,并提供了详细的代码实现。
18 0
LeetCode第二十四题(两两交换链表中的节点)
|
1月前
Leetcode第十九题(删除链表的倒数第N个节点)
LeetCode第19题要求删除链表的倒数第N个节点,可以通过快慢指针法在一次遍历中实现。
44 0
Leetcode第十九题(删除链表的倒数第N个节点)
|
1月前
|
索引
力扣(LeetCode)数据结构练习题(3)------链表
力扣(LeetCode)数据结构练习题(3)------链表
78 0
|
1月前
【LeetCode 10】142. 环形链表 II
【LeetCode 10】142. 环形链表 II
21 0
|
1月前
【LeetCode 09】19 删除链表的倒数第 N 个结点
【LeetCode 09】19 删除链表的倒数第 N 个结点
17 0
|
1月前
【LeetCode 08】206 反转链表
【LeetCode 08】206 反转链表
12 0
|
1月前
【LeetCode 06】203.移除链表元素
【LeetCode 06】203.移除链表元素
30 0
|
3月前
|
算法
LeetCode第24题两两交换链表中的节点
这篇文章介绍了LeetCode第24题"两两交换链表中的节点"的解题方法,通过使用虚拟节点和前驱节点技巧,实现了链表中相邻节点的交换。
LeetCode第24题两两交换链表中的节点