【算法】day13 链表
1、合并 K 个升序链表 hot
题目:23. 合并 K 个升序链表 - 力扣(LeetCode)

分析:
(假设所有链表长度为 n,有 k 个链表)
法一,暴力解法:回想两个有序游标合并,合并一次时间复杂度 O(2n)=O(n)。那么第一个链表与后续的 k-1 个链表合并,需要遍历第一个链表 (k-1)n 次节点。那么 k 个链表合并,需要遍历 (k-1)n + (k-2)n + …… + 2n + n = n*k(k-1)/2 = O(n*k^2),相当于 O(n^3) 时间复杂度很高。

法二,优先队列(堆):每次把 k 个链表的头结点取出来比较,将最小的放入新链表,并将其遍历指针往后移,时间复杂度就是所有链表的长度和 O(kn)。但我们还需要获得 k 个节点中哪个最小,就可以用到大小为 k 的小根堆,每次取出堆顶的元素插入新链表然后往后移动,移动到的节点再插入堆,直到堆为空。因为堆中有 k 个节点,所以构建堆 O(klogk),后续假设每个节点插入、删除堆 O(kn*2logk),总时间复杂度:O(n*klogk)。空间复杂度,堆大小:O(k)。
法三,分治(递归):类似于归并排序,把数字元素替换为链表元素,然后我们合并的不是左右子数组,而是左右链表。因为有 k 个节点(链表),所以树高 logk,每层最多需要遍历 kn 个节点,时间复杂度:O(n*klogk)。空间复杂度,递归深度:O(logk)。

代码:
优先队列:
/*** Definition for singly-linked list.* public class ListNode {* int val;* ListNode next;* ListNode() {}* ListNode(int val) { this.val = val; }* ListNode(int val, ListNode next) { this.val = val; this.next = next; }* }*/
class Solution {public ListNode mergeKLists(ListNode[] lists) {// 1. 构建小根堆,把所有头结点插入堆PriorityQueue<ListNode> heap = new PriorityQueue<>((node1, node2) -> node1.val-node2.val);for (ListNode list : lists)if (list != null) heap.offer(list);// 2. 只要堆不为空,就取出堆顶尾插入新链表,并将指针后移ListNode newList = new ListNode();ListNode tail = newList;while (!heap.isEmpty()) {ListNode min = heap.poll();tail.next = min;tail = min;if (min.next != null) heap.offer(min.next);}return newList.next;}
}
递归:
/*** Definition for singly-linked list.* public class ListNode {* int val;* ListNode next;* ListNode() {}* ListNode(int val) { this.val = val; }* ListNode(int val, ListNode next) { this.val = val; this.next = next; }* }*/
class Solution {public ListNode mergeKLists(ListNode[] lists) {return mergeLists(lists, 0, lists.length-1);}public ListNode mergeLists(ListNode[] lists, int l, int r) {// 递归结束条件if (l > r) return null;if (l == r) return lists[l];// 划分左右链表子数组,递归合并int mid = l+(r-l)/2;ListNode list1 = mergeLists(lists, l, mid);ListNode list2 = mergeLists(lists, mid+1, r);// 合并左右链表return mergeTwoLists(list1, list2);}public ListNode mergeTwoLists(ListNode list1, ListNode list2) {ListNode newHead = new ListNode();ListNode tail = newHead;while (list1 != null && list2 != null) {if (list1.val <= list2.val) {tail.next = list1;list1 = list1.next;} else {tail.next = list2;list2 = list2.next;}tail = tail.next;}if (list1 != null) tail.next = list1;if (list2 != null) tail.next = list2;return newHead.next;}
}
2、K 个一组翻转链表 hot
题目:25. K 个一组翻转链表 - 力扣(LeetCode)

分析:翻转链表,可以想到遍历链表节点,头插到新链表。我们可以分成 length/k 组进行翻转头插,最后 n%k 个不管。每组从 tmp 位置(上一组遍历的第一个节点)开始头插。时间复杂度 O(n),空间:O(1)。

代码:
/*** Definition for singly-linked list.* public class ListNode {* int val;* ListNode next;* ListNode() {}* ListNode(int val) { this.val = val; }* ListNode(int val, ListNode next) { this.val = val; this.next = next; }* }*/
class Solution {public ListNode reverseKGroup(ListNode head, int k) {// 1. 求链表长度int n = 0;ListNode t = head;while (t != null) {n++;t = t.next;}// 2. 划分成 n/k 组,每组遍历 k 个节点int m = n/k;ListNode cur = head;ListNode newHead = new ListNode(), tail = newHead;for (int i = 0; i < m; i++) {ListNode tmp = cur;for (int j = 0; j < k; j++) {// 执行头插ListNode next = cur.next;cur.next = tail.next;tail.next = cur;// 遍历到下一个节点cur = next;}// 下一组,从 tmp 节点开始头插tail = tmp;}// 3. 把剩下凑不够 k 个的节点,直接插到末尾tail.next = cur;return newHead.next;}
}
3、反转链表 hot
题目:206. 反转链表 - 力扣(LeetCode)

分析:上一个题都会做了,这个还不会?就是头插。时间复杂度 O(n),空间 O(1)。
代码:
/*** Definition for singly-linked list.* public class ListNode {* int val;* ListNode next;* ListNode() {}* ListNode(int val) { this.val = val; }* ListNode(int val, ListNode next) { this.val = val; this.next = next; }* }*/
class Solution {public ListNode reverseList(ListNode head) {if (head == null || head.next == null) return head;// 遍历,头插到新链表ListNode newHead = new ListNode();while (head != null) {ListNode next = head.next;head.next = newHead.next;newHead.next = head;head = next;}return newHead.next;}
}