一、概述
- K条有序链表合并,按顺序合并链表如:
- 链表A:1-3-5
- 链表B:2-4
- 链表C:3-6-7
- 链表D:7-8-9
- 链表E:10-11
- 合并结果:1-2-3-4-5-6-7-8-9
二、代码
构建A、B、C、D、E 5条链表。
public static void main(String[] args) {
// 构造链表A
LinkNode linkNodeA1 = new LinkNode(1);
LinkNode linkNodeA2 = new LinkNode(3);
LinkNode linkNodeA3 = new LinkNode(5);
linkNodeA1.next = linkNodeA2;
linkNodeA2.next = linkNodeA3;
// 构造链表B
LinkNode linkNodeB1 = new LinkNode(2);
LinkNode linkNodeB2 = new LinkNode(4);
linkNodeB1.next = linkNodeB2;
// 构造链表C
LinkNode linkNodeC1 = new LinkNode(3);
LinkNode linkNodeC2 = new LinkNode(6);
LinkNode linkNodeC3 = new LinkNode(7);
linkNodeC1.next = linkNodeC2;
linkNodeC2.next = linkNodeC3;
// 构造链表D
LinkNode linkNodeD1 = new LinkNode(7);
LinkNode linkNodeD2 = new LinkNode(8);
LinkNode linkNodeD3 = new LinkNode(9);
linkNodeD1.next = linkNodeD2;
linkNodeD2.next = linkNodeD3;
// 构造链表E
LinkNode linkNodeE1 = new LinkNode(10);
LinkNode linkNodeE2 = new LinkNode(11);
linkNodeE1.next = linkNodeE2;
// 合并链表
LinkNode linkNode = merge5(new LinkNode[]{linkNodeA1, linkNodeB1, linkNodeC1, linkNodeD1, linkNodeE1});
while (null != linkNode) {
System.out.println(linkNode.obj);
linkNode = linkNode.next;
}
}
static class LinkNode {
// 数据
public Integer obj;
// 下一个结点
public LinkNode next;
public LinkNode(Integer obj) {
super();
this.obj = obj;
}
}
public static LinkNode linkMerge(LinkNode ln1, LinkNode ln2) {
if(null == ln1) return ln2;
if(null == ln2) return ln1;
// 设置虚拟头结点
LinkNode head = new LinkNode(-1);
// temp 指向每一次合并之后的最后一个结点:用于指定下一个结点
LinkNode temp = head;
while (null != ln1 && null != ln2) {
if(ln1.obj <= ln2.obj) {
temp.next = ln1;
temp = ln1;
ln1 = ln1.next;
} else {
temp.next = ln2;
temp = ln2;
ln2 = ln2.next;
}
}
if(null == ln1) {
temp.next = ln2;
} else {
temp.next = ln1;
}
return head.next;
}
思路1:最笨的办法:时间复杂度是O(nlogn),空间复杂度是O(n)
- 先添加到数组 -> 排序 -> 生成链表
public static LinkNode merge(List<LinkNode> linkNodes) {
if(null == linkNodes || linkNodes.size() == 0) return null;
// 1、添加到数据 时间负责度O(n)
List<LinkNode> nodes = new ArrayList<Links.LinkNode>();
for(LinkNode linkNode : linkNodes) {
while (null != linkNode) {
nodes.add(linkNode);
linkNode = linkNode.next;
}
}
// 2、排序 时间负责度O(nlogn)
nodes.sort((LinkNode node1, LinkNode node2) -> {
return node1.obj - node2.obj;
});
// 3、生成新的链表 时间负责度O(n)
LinkNode linkNode = new LinkNode(-1);
LinkNode cur = linkNode;
for (LinkNode node : nodes) {
if(cur.obj == node.obj) {
continue;
}
cur.next = node;
cur = node;
}
return linkNode.next;
}
思路2、逐一比较法,跟Link类中的方法类似(取每一条链表中的第一个元素比较,往复循环),时间复杂度:O(nk)
- while中循环次数k = 所有节点的个数
- for循环的次数为链表的条数n
- 所以时间复杂度为O(nk)
- 第一种方法的时间度为O(nlogn),所以第一种和第二种方法那种好就看nk和nlogn值的大小了,也就是k和logn值的大小了,计算出来的值哪个小,就更适合用哪种方法
public static LinkNode merge2(LinkNode[] linkNodes) {
LinkNode firstLinkNode = new LinkNode(-1);
LinkNode cur = firstLinkNode;
while (true) {
int minIndex = -1;
for(int i = 0; i < linkNodes.length; i++) {
LinkNode linkNode = linkNodes[i];
if(null == linkNode) {
continue;
}
if(-1 == minIndex || linkNode.obj < linkNodes[minIndex].obj) {
minIndex = i;
}
}
if(-1 == minIndex) {
break;
}
System.out.println("第条"+minIndex+"链表最小,值为:" + linkNodes[minIndex].obj);
// 将下一个结点指向最小的一条链表的头部
if(cur.obj != linkNodes[minIndex].obj) {
cur.next = linkNodes[minIndex];
cur = linkNodes[minIndex];
}
linkNodes[minIndex] = linkNodes[minIndex].next;
}
return firstLinkNode.next;
}
思路3、两两比较,跟Link中一样,然后用生成的新链表和下一条链表进行合并,时间复杂度:O(nk)
public static LinkNode merge3(LinkNode[] linkNodes) {
if(null == linkNodes || linkNodes.length == 0) {
return null;
}
LinkNode linkNode = linkNodes[0];
for(int i = 1; i < linkNodes.length; i++) {
linkNode = linkMerge(linkNode, linkNodes[i]);
}
return linkNode;
}
- 第一个for循环 时间复杂度是O(n)
- linkMerge方法的时间复杂度是n次ln1的长度+ln2的长度,并且每次ln1的长度或ln2的长度是变化的。
- 所以假设有n个结点,k条链表,每一条链表的长度就是n/k,因此k-1第的总和为:
(n/k+n/k) + (2*n/k+n/k) + (3*n/k+n/k) + ... + ((k-1)*n/k+n/k)
2*n/k + 3*n/k + 4*n/k + k*n/k
[(2+k)*(k-1)/2]*n/k
O(k^2 * n/k)
O(kn)
所以时间复杂度也为O(nk)
思路4、优先级队列(小顶堆) ,时间复杂度:O(nlogk),空间复杂度O(k)
public static LinkNode merge4(LinkNode[] linkNodes) {
if(null == linkNodes || linkNodes.length == 0) {
return null;
}
LinkNode head = new LinkNode(-1);
LinkNode cur = head;
// 将所有节点的头结点添加到优先队列(小顶堆)中
PriorityQueue<LinkNode> queue = new PriorityQueue<>((LinkNode node1, LinkNode node2) -> {
return node1.obj - node2.obj;
});
// 因为队列里有k个元素,所以添加元素到优先队列的时间复杂度是logk,因为循环k次,所以这个for循环的时间复杂度为O(klogk)
for(LinkNode node : linkNodes) {
queue.offer(node);
}
// 不断删除推顶元素,并且把堆顶元素的next添加到集合中
// 因为队列里有k个元素,所以添加元素到优先队列的时间复杂度是logk,,因为要将n个节点串联删除和串联起来,所以这个for循环的时间复杂度为O(n2logk),去除常数为O(nlogk)
while (!queue.isEmpty()) {
LinkNode linkNode = queue.poll(); // O(logk)
cur.next = linkNode;
cur = linkNode;
if(null != linkNode.next) {
queue.offer(linkNode.next); // O(logk)
}
}
return head.next;
}
- 空间复杂度O(k),在创建队列的时候将K条链表加到优先级队列,也就是说将k个元素添加到优先级队列
- 时间复杂度: 第一个for循环klogk, while循环时间复杂度nlogk,所以时间复杂度为O(nlogk)
思路5、分治策略,时间复杂度:O(nlogk)
public static LinkNode merge5(LinkNode[] linkNodes) {
if(null == linkNodes || linkNodes.length == 0) {
return null;
}
// 这种方法时间复杂度O(nlogk)
int step = 1;
while (step < linkNodes.length) { // 循环logk次 O(logk)
for(int i = 0; i < linkNodes.length; i = (i+2*step)) { // 将所有节点串联起来:O(n)
if(i+step >= linkNodes.length) {
break;
}
linkNodes[i] = linkMerge(linkNodes[i],linkNodes[i+step]);
}
step = step*2;
}
return linkNodes[0];
// 便于理解 时间复杂度O(nlogk)
// LinkedList<LinkNode> list = new LinkedList<>();
// ArrayList<LinkNode> templist = new ArrayList<>(); // 每次合并链表之后存放的合并之后的链表集合
// for(LinkNode linkNode : linkNodes) { // O(k)
// list.add(linkNode);
// }
// // 当为基数时,取出最后一位,让链表为偶数条,执行分之策略 -> 然后在和最后一条合并
// LinkNode lastLinkNode = null;
// if(list.size() % 2 != 0) {
// lastLinkNode = list.removeLast();
// }
// while (list.size() > 1) { // 循环logk次 O(logk)
// // 清空集合元素
// remove(templist);
// for(int i = 0; i < list.size(); i=i+2) { // 将所有节点串联起来:O(n)
// // 合并链表
// linkNodes[i] = linkMerge(list.get(i), list.get(i+1));
// templist.add(linkNodes[i]);
// }
// // 清空集合元素
// remove(list);
// list.addAll(templist);
// }
// if(null != lastLinkNode) {
// return linkMerge(list.get(0), lastLinkNode);
// } else {
// return list.get(0);
// }
}