两有序数组两两之和的最小k个值, 最小堆解法之完整版
2013-11-26 23:43
393 查看
原题: 给定两有序数组,长度都是n,在总共n^2个两两之和中,找到最小的k个值。
题目不新鲜,出现了也有很多年了,不过在目前的面试中还有使用。较好的方法显然是用最小堆,时间复杂度降到 O(klgk)不过网上大略搜了一下,没有找到完整实现,甚至连关键点都没提到,我这里就算是个补充吧。
用最小堆,元素是 A[i] + B[j]. 每次弹出堆顶,然后插入新元素,维护最小堆。这里的关键在于每次插入哪些元素。大多数人说的都是,对于弹出A[i] + B[j], 应该插入A[i+1] + B[j], 以及A[i] + B[j+1]。我们来看看事实是什么。
已有A,B 两数组,假设从0开始升序排列。
如果当前最小堆弹出了A[1]+B[0], 那么接下来显然应该插入A[1] + B[1], 同时还有A[2] + B[0]. A[3]以上的组合不必考虑,因为A[2] + B[0]才刚刚插入。
如果当前弹出了A[1] + B[1], 那么插入A[1] + B[2]. 还有其他要插的么? 其实已经没还有了。由于A[2] + B[0]已在堆中还未弹出,所以A[3] 以上的组合全都不用入堆。
简单列个表格如下, 弹出项除(0,0)外全都是假设:
selected(i, j) heap
(0,0) (0,1), (1,0)
(0,1) (0,2), (1,0)
(1,0) (0,2), (1,1), (2,0)
(0,2) (0,3), (1,1), (2,0)
(2,0) (0,3), (1,1), (2,1), (3,0) ------------ 局部冗余
(1,1) (0,3), (1,2), (2,1), (3,0)
...
这下就很明白了, 每次弹出(i,j),至少插入一个值(i, j+1). 只有当j=0时,才插入(i+1, 0)
对于上面的过程,可能有细心人发现,在某些时候,堆中会有冗余。比如对于以上弹出(2, 0)时, 插入(2, 1),由于(1,1)此时还未弹出,所以(2, 1)其实没必要插入。
解释有二: 对于这种插入方法,这是(2, 1)插入堆的唯一机会;而对于容量为K的最小堆来说,额外(应该叫提早)插入一个较大值,其实无影响,反正对于所有操作,都是O(lgk)级别。
除了这个“梗”之外,便是一些实现细节了。这里只需要两个最小堆的操作: 弹出 和 插入。我用空指针来表示无限大节点,这样,在节点作比较和交换时,有一些边界处理。
这里摘出最小堆弹出和插入的函数。
完整程序见 https://github.com/teaspring/problems/blob/master/InterviewQ/src/findminksum.cpp, 在g++ 4.6下编译通过可运行。
题目不新鲜,出现了也有很多年了,不过在目前的面试中还有使用。较好的方法显然是用最小堆,时间复杂度降到 O(klgk)不过网上大略搜了一下,没有找到完整实现,甚至连关键点都没提到,我这里就算是个补充吧。
用最小堆,元素是 A[i] + B[j]. 每次弹出堆顶,然后插入新元素,维护最小堆。这里的关键在于每次插入哪些元素。大多数人说的都是,对于弹出A[i] + B[j], 应该插入A[i+1] + B[j], 以及A[i] + B[j+1]。我们来看看事实是什么。
已有A,B 两数组,假设从0开始升序排列。
如果当前最小堆弹出了A[1]+B[0], 那么接下来显然应该插入A[1] + B[1], 同时还有A[2] + B[0]. A[3]以上的组合不必考虑,因为A[2] + B[0]才刚刚插入。
如果当前弹出了A[1] + B[1], 那么插入A[1] + B[2]. 还有其他要插的么? 其实已经没还有了。由于A[2] + B[0]已在堆中还未弹出,所以A[3] 以上的组合全都不用入堆。
简单列个表格如下, 弹出项除(0,0)外全都是假设:
selected(i, j) heap
(0,0) (0,1), (1,0)
(0,1) (0,2), (1,0)
(1,0) (0,2), (1,1), (2,0)
(0,2) (0,3), (1,1), (2,0)
(2,0) (0,3), (1,1), (2,1), (3,0) ------------ 局部冗余
(1,1) (0,3), (1,2), (2,1), (3,0)
...
这下就很明白了, 每次弹出(i,j),至少插入一个值(i, j+1). 只有当j=0时,才插入(i+1, 0)
对于上面的过程,可能有细心人发现,在某些时候,堆中会有冗余。比如对于以上弹出(2, 0)时, 插入(2, 1),由于(1,1)此时还未弹出,所以(2, 1)其实没必要插入。
解释有二: 对于这种插入方法,这是(2, 1)插入堆的唯一机会;而对于容量为K的最小堆来说,额外(应该叫提早)插入一个较大值,其实无影响,反正对于所有操作,都是O(lgk)级别。
除了这个“梗”之外,便是一些实现细节了。这里只需要两个最小堆的操作: 弹出 和 插入。我用空指针来表示无限大节点,这样,在节点作比较和交换时,有一些边界处理。
这里摘出最小堆弹出和插入的函数。
template<typename T> T minpop(T *A, int& size, int n){ T res = A[1]; A[1]=0; int i = 1; while(i<size){ int l = Left(i); int r = Right(i); int less = i; if(l<=size && A[l] != 0){ less = l; } if(r<=size && A[r] != 0){ if(A[less]==0 || *(A[r]) < *(A[less])){ less = r; } } if(less == i) break; if(A[i]==0){ //swap [less] and [i], and A[less] will not be 0 now A[i] = A[less]; A[less] = 0; }else{ myswap(A[less], A[i]); } i = less; } --size; return res; } template<typename T> void mininsert(T *A, int n, int& size, T ele){ //there will be 0 element in array if(size > n) return; int i=size+1; A[i] = ele; while(i>1){ //move A[i] upwards if it is less than parent int p = Parent(i); if(p>0 && *(A[p]) < *(A[i])) //for insertion, [p] will not be 0 break; myswap(A[p], A[i]); i = p; } ++size; return; }
完整程序见 https://github.com/teaspring/problems/blob/master/InterviewQ/src/findminksum.cpp, 在g++ 4.6下编译通过可运行。
相关文章推荐
- 算法-求两个有序数组两两相加的值最小的K个数
- 算法-求两个有序数组两两相加的值最小的K个数
- 【面试题】求两个有序数组两两相加的值最小的K个数
- 求两个有序数组两两相加的值最小的K个数
- 程序员面试金典: 9.4树与图 4.3给定一个有序整数数组,元素各不相同且按升序排列,创建一颗高度最小的二叉查找树。 ---快速解法
- 求两个有序数组两两相加的值最小的K个数
- C++求有序数组旋转之后的最小数字
- 有序数组的旋转数组的最小值
- 两个有序数组,A[k]和B[k]长度都为k。求前k个最小的(a[i]+b[j])
- 一道面试题:有一个整数数组,请求出两两之差绝对值最小的值,记住,只要得出最小值即可,不需要求出是哪两个数。
- 算法导论第三版第六章 合并K个有序链表的三种解法(最小堆法和分治递归法)
- 求最小绝对值子串、一个整数数组求两两之差绝对值最小值
- 【转】[面试题] 求数组两两之差绝对值最小的值
- uva 12356 Army Buddies 树状数组解法 树状数组求加和恰为k的最小项号 难度:1
- 转:最小区间:k个有序的数组,找到最小区间使k个数组中每个数组至少有一个数在区间中
- [LeetCode] 153. Find Minimum in Rotated Sorted Array 寻找旋转有序数组的最小值
- 笔试题:写一个有序整数数组两两之和等于某个数所有组合
- 有一个整数数组,请求出两两之差绝对值最小的值
- 有一个整数数组,请求出两两之差绝对值最小的值,记住,只要得出最小值即可,不需要求出是哪两个数。 (微软面试题)
- 给定一个递增有序数组,要求构建一棵具有最小高度的二叉查找树