题目
合并K个升序链表
方法1 合并分治
归并排序思路:合并K个有序数列(递归 + 合并相邻两个子序列)
此题思路可借鉴为:递归 + 合并2个有序链表
子问题 LC21 合并2个有序链表(递归)
/**
* 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 mergeTwoLists(ListNode l1, ListNode l2) {
if(l1 == null){
return l2;
}
if(l2 == null){
return l1;
}
if(l1.val < l2.val){
l1.next = mergeTwoLists(l1.next, l2);
return l1;
}
else{
l2.next = mergeTwoLists(l1, l2.next);
return l2;
}
}
}
归并排序的主题思想就是通过递归合并k个有序数组。可以参考归并排序的写法。
归并排序代码
import java.util.Arrays;
public class MergeSort {
public static void main(String[] args) {
int[] arr = {8,4,5,7,1,3,6,2};
int[] temp = new int[arr.length];
mergeSort(arr, 0, arr.length-1, temp);
System.out.println("sorted:" + Arrays.toString(arr));
}
//分解的方法
public static void mergeSort(int[] arr, int left, int right, int[] temp) {
if(left < right) {
int mid = (left + right) / 2;
//向左分解
mergeSort(arr, left, mid, temp);
//向右
mergeSort(arr, mid+1, right, temp);
//到这一步为止,将所有元素分割成最小单位,输出为{8,4,5,7,1,3,6,2}
//合并
merge(arr, left, mid, right, temp);
}
}
//这个函数的目的是合并相邻两个子序列:合并的方法
/**
* @param arr 待排序的数组
* @param left 左边有序序列的初始索引
* @param mid 左边有序序列的最后一个索引
* mid+1 右边有序序列的初始索引
* @param right 右边有序序列的最后一个索引
* @param temp 中转数组
*/
public static void merge(int[] arr, int left, int mid, int right, int[] temp) {
int i = left;//初始化i, 左边有序序列的初始索引
int j = mid + 1;//右边有序序列的初始索引
int t = 0;//指向temp数组的当前索引
//第一步,将左右两边的数比大小,依次拷贝到temp数组,直到左右两边有一边处理完毕
while(i <= mid && j <= right) {
if(arr[i] < arr[j]) {
temp[t] = arr[i];//temp从0开始,谁小就
t++;
i++;
}
else {
temp[t] = arr[j];
t++;
j++;
}
}
//第二步,将有剩余数据的一方填充到temp数组
while(i <= mid) {//左边的序列有剩余,全部填充到temp,当i等于mid的时候还有1个数留在左边,所以必须是小于等于
temp[t] = arr[i];
i++;
t++;
}
while(j <= right) {//右边的序列有剩余,全部填充到temp
temp[t] = arr[j];
j++;
t++;
}
//第三步,将temp数组拷回arr
//注意!并不是每次都拷贝所有元素,这个是用于递归过程的
t = 0;
int tempLeft = left;
while(tempLeft <= right) {
arr[tempLeft] = temp[t];
t++;
tempLeft++;
}
}
}
k个有序链表代码
/**
* 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 merge(lists, 0, lists.length - 1);
}
//分解+递归
public ListNode merge(ListNode[] lists, int left, int right){
if(right == left){
return lists[left];
}
if(left > right){
return null;
}
else{
int mid = (left + right) / 2;
return mergeTwoLists(merge(lists, left, mid), merge(lists, mid+1, right));
}
}
//合并 两个相邻的子链表
public ListNode mergeTwoLists(ListNode l1, ListNode l2) {
if(l1 == null){
return l2;
}
if(l2 == null){
return l1;
}
if(l1.val < l2.val){
l1.next = mergeTwoLists(l1.next, l2);
return l1;
}
else{
l2.next = mergeTwoLists(l1, l2.next);
return l2;
}
}
}
方法2 优先队列
思路:分别取每个链表的头节点比较,将最小的作为新链表的第一个节点。在选取最小元素的时候,我们可以用优先队列来优化这个过程。
class Solution {
public ListNode mergeKLists(ListNode[] lists) {
if(lists == null || lists.length <= 0){
return null;
}
PriorityQueue<ListNode> pq = new PriorityQueue<>(cmp);
for(ListNode node:lists){
if(node != null){
pq.add(node);//将每列最靠前的元素加入到队列中
}
}
//哑节点
ListNode dummy = new ListNode(0);
//尾插法,将pq中的ListNode建立成链表
ListNode tail = dummy;
while(!pq.isEmpty()){
ListNode curr = pq.poll();
tail.next = curr;
//因为每次都是将新节点插入到链表尾部,所以需要加入一个始终指向链表尾部的指针tail,以便于让新节点插入到链表尾部
tail = tail.next;
if(curr.next != null){
pq.add(curr.next);//将后边的链表顶替上来
}
}
return dummy.next;
}
Comparator<ListNode> cmp = new Comparator<ListNode>(){
public int compare(ListNode o1, ListNode o2) {
return o1.val - o2.val;//升序
}
};
}