合并k个有序链表

一、分治法


  一说起算法对我而言就不由的想起暴力、分治、回溯、动态规划、贪心、深度优先、广度优先等等,不过我一个都不精通。-V-!!。好了不扯了,进入正题,看标题“分治大法好”,为什么这么起嘞,是有原因的。博主今天在刷LeetCode的时候遇到了这么个题,合并K个有序链表,题号是23,我这里给了链接,有兴趣的可以去看看,我今天就来用分治的策略来分析一下这个题目。

合并k个有序链表_第1张图片

  接下来就来针对这个题来分析一下。从比较简单的思想来逐渐深入。


二、思路

  (1)首先以我的角度第一个想法是先将第一个链表和第二个链表合并,然后再将合并好的链表与第三个链表去合并,依次类推就可将问题解决。但是这么做弊端也很明显就是时间复杂度比较大,假设链表长度都为N,那么时间复杂度应该为O(n!),所以并不是很好的方法。
  (2)第二个就是归并的思想,也就是将第一个链表和第二个链表合并,再将第三个链表和第四个链表合并,依次类推,再将和并好的在合并就得到了最终的那个链表。这种时间复杂度相比上一个就要小一部分了。
如图:
合并k个有序链表_第2张图片

  (3)第三种就是使用堆这个数据结构,直接对k个链表同时依次向后遍历,那么时间复杂度应该在n(logn)可以说是最理想的一种方法了。

  不过博主还未进一步了解这种处理方法,因为能将问题和数据结构联系到一块还不是博主所能触及到的区域。这里就提下,今天主要想说的是 关于第二种思路和分治结合在一起的处理方法。


三、分析

  当看到上面那个合并图的时候,相信敏感的同学很快就能发现这是一个树状的结构且还是最规矩的二叉树状,换言之就是“问题可分为若干个子问题,且子问题的解可构成原问题的解”,这是很明显的分治特征。看到这里那么问题解决思路就很清晰了。就是将K个链表分为两部分,然后递归下去就是问题的解了。这样表述是站在宏观的角度上的,可能有些人并不是很明白。没关系这很正常,你可以这么想,也就是将K看成2,也就是只有两条链表,然后将其合并,是不是就能理解开了。好了光说不练是不行的,上代码,结合代码可能就明白了。


四、代码

这里我用的是c++不过语言大同小异,注重思想。

	 class Solution{
		public:
			ListNode* mergeTwoLists(ListNode* l1,ListNode* l2){
				//情况预处理以及递归结束条件
				if(l1 == NULL) return l2;
				if(l2 == NULL) return l1;
				
				//这段代码也是相当精髓的。同样的你可以看作是l1和l2都只有一个节点。
				ListNode* head = NULL;
				if(l1->value <= l2->value)
				{
					head = l1;
					head -> next = mergeTwoLists(l1->next,l2);
				}
				else
				{
					head = l2;
					head -> next = mergeTwoLists(l1,l2->next);
				}
				return head;
			}
			ListNode* mergeKLists(vector& lists){
				//情况预处理以及递归结束条件,基本每道题一开始都要考虑这个的。
				if(lists.size()==0) return NULL;
				if(lists.size()==1) return lists[0];
				if(lists.size()==2) return  mergeTwoLists(lists[0],lists[1]);
				
				//一分为二
				int mid = lists.size()/2;
				vector l1(mid);
				vector l2(lists.size()-mid);
				for(int i=0;i

五、总结

  我觉得看到到这里大家应该就会明白我为什么要起这么个标题了,将实在的但我看到这种解法的时候真的惊到了,应为即使你有第二种思路但想将它用代码完整的翻译出来实质上是有一定难度的,少不免要出点小bug,但是加入分治思想后整体代码逻辑真的是非常清晰了,这段代码真的可以说是优美了,没有一点拖泥带水。而且难能可贵的是这道题目也是与分治异常的契合,将分治的思想可谓是发挥到了极致。当然这都是我的愚见。虽然效率上并不及第三种处理方法,不过我认为整体上堪称完美。可能是我阅历还太浅,但是这段代码真的有震撼到我。好了今天的分享就到这了。这也算是我真正意义上的第一篇博客了,希望大家喜欢。

你可能感兴趣的:(算法)