题目:
有两个已经排好序的数组,如何将这两个数组合并且合并后的数组仍然是有序的?
试题分析:
这一道题目是一位同学和大家分享的他去百度面试web前端开发的职位时面试官问道的一道算法题目。看起来貌似和我们的题目没有关系,但是如果你知道归并排序的核心实现你应该能立马反应过来。如果你不知道也没有关系,在我们下面的一番分析之后你应该就能明白了。
其实解决一些算法问题的途径有很多,分治法就是其中的一种。分治法从语义即可知道,就是“分而治之”,把原来的一个大问题分解成一个个的很小的小问题,然后分别去解决小问题之后再把答案合并起来,组成大问题的解,各个击破,循序渐进,最后把问题顺利成章的解决掉。
如果一个比较大的问题能够分解成许多比较小的问题,而且这些小问题的结构、解法与大问题相类似,这个时候我们可以利用递归这一神器巧妙地来解决问题。递归的优点很明显,结构条理清晰,能够有效地减少代码量,但缺点是在数据量较大的时候效率不高,会占用额外的内存。
归并排序就是利用了分而治之、递归解决问题的思路。如果两个数组都是有序的,那么我们可以定义一个新的数组,然后两个数组都从0开始依次分别比较两个数组中值得大小,较小的放进新数组中并将位置后移一位,知道最后把两个数组所有的值都放进新数组中。这样算法的时间复杂度是O(m+n)的,即为两个数组的长度之和。
这个问题其实是很简单的。那么它究竟和归并排序有毛关系呢?我们接着思考归并排序。
我们可以把一个大数组分成两个较小的数组,然后其中每一个较小的数组又可以分成两个较小的数组,一直分到最后,也就是大数组中的每一项都成为一个独立的小数组,而且都是有序的(只有一项的数组当然有序)。这个时候,我们可以利用上面把两个有序数组合并成为一个数组的算法将其中两个数组合并为一个,然后这一个有两项的数组是有序的,同样的再将另外两项合并,所有的都合并完之后再将这些两项的有序数组合并,依次进行下去,直到只剩下两个大的有序数组,再合并即可得到有序的一个数组。
这里的实现可能和上面说的稍微有点出入,但是基本的思想是一致的。我们只是没有把数组拆分,而是来操作该数组的子数组(从哪一项到另一项,比如从p到q)。可以将代码放到编译器中运行调试一下看下结果。
有两个已经排好序的数组,如何将这两个数组合并且合并后的数组仍然是有序的?
试题分析:
这一道题目是一位同学和大家分享的他去百度面试web前端开发的职位时面试官问道的一道算法题目。看起来貌似和我们的题目没有关系,但是如果你知道归并排序的核心实现你应该能立马反应过来。如果你不知道也没有关系,在我们下面的一番分析之后你应该就能明白了。
其实解决一些算法问题的途径有很多,分治法就是其中的一种。分治法从语义即可知道,就是“分而治之”,把原来的一个大问题分解成一个个的很小的小问题,然后分别去解决小问题之后再把答案合并起来,组成大问题的解,各个击破,循序渐进,最后把问题顺利成章的解决掉。
如果一个比较大的问题能够分解成许多比较小的问题,而且这些小问题的结构、解法与大问题相类似,这个时候我们可以利用递归这一神器巧妙地来解决问题。递归的优点很明显,结构条理清晰,能够有效地减少代码量,但缺点是在数据量较大的时候效率不高,会占用额外的内存。
归并排序就是利用了分而治之、递归解决问题的思路。如果两个数组都是有序的,那么我们可以定义一个新的数组,然后两个数组都从0开始依次分别比较两个数组中值得大小,较小的放进新数组中并将位置后移一位,知道最后把两个数组所有的值都放进新数组中。这样算法的时间复杂度是O(m+n)的,即为两个数组的长度之和。
这个问题其实是很简单的。那么它究竟和归并排序有毛关系呢?我们接着思考归并排序。
我们可以把一个大数组分成两个较小的数组,然后其中每一个较小的数组又可以分成两个较小的数组,一直分到最后,也就是大数组中的每一项都成为一个独立的小数组,而且都是有序的(只有一项的数组当然有序)。这个时候,我们可以利用上面把两个有序数组合并成为一个数组的算法将其中两个数组合并为一个,然后这一个有两项的数组是有序的,同样的再将另外两项合并,所有的都合并完之后再将这些两项的有序数组合并,依次进行下去,直到只剩下两个大的有序数组,再合并即可得到有序的一个数组。
其思想还是比较直观的,我们可以利用递归的方法去直接实现,js代码如下:
// 数组ary从p到q是有顺序的,从q到r是有顺序的
function merge(ary,p,q,r){
var a = [],
b = [];
for(var i = p;i<=q;i++){
a[a.length] = ary[i];
}
for(i = q+1;i<r+1;i++){
b[b.length] = ary[i];
}
a[a.length] = Infinity;
b[b.length] = Infinity;
var newAry = [],
m = 0,
n = 0,
len;
for(i = 0,len = r-p+1;i<len;i++){
if(a[m] <= b[n]){
ary[p+i] = a[m];
m++;
}else{
ary[p+i] = b[n];
n++;
}
}
return ary;
}
console.log(merge([12,9,10,1,3],1,2,4));
function mergeSort(ary,p,r){
var q;
if(r - p === 1 || (r === p)){
q = p;
return merge(ary,p,q,r);
}else{
q = Math.ceil((p+r)/2);
mergeSort(ary,p,q);
mergeSort(ary,q+1,r);
return merge(ary,p,q,r);
}
}
var ary = [2,5,3,1,4,6,8,9,7,10,23,12,7],
len = ary.length;
console.log(mergeSort(ary,0,len-1)); //[1, 2, 3, 4, 5, 6, 7, 7, 8, 9, 10, 12, 23]
这里的实现可能和上面说的稍微有点出入,但是基本的思想是一致的。我们只是没有把数组拆分,而是来操作该数组的子数组(从哪一项到另一项,比如从p到q)。可以将代码放到编译器中运行调试一下看下结果。