一、Divide and Conquer
You are interested in analyzing some hard-to-obtain data from two separate databases. Each database contains n numerical values, so there are 2n values total and you may assume that no two values are the same. You’d like to determine the median of this set of 2n values, which we will define here to be the n-th smallest value.
However, the only way you can access these values is through queries to the databases. In a single query, you can specify a value k to one of the two databases, and the chosen database will return the kth smallest value that it contains. Since queries are expensive, you would like to compute the median using as few queries as possible.
Give an algorithm that finds the median value using at most O(logn) queries.
1、problem-solving ideas and pseudo-code
首先将现实问题转化一下,变成计算机算法问题,即 找2个有序数组的并集的中位数 ,LeetCode 第四题。
(1)problem-solving ideas
求中位数需要根据数组长度是奇数还是偶数分别讨论,奇数长度时中位数为最中间的一个数,偶数长度时中位数为最中间的两个数的平均值,为了方便,可以实现一个比题目更一般化的函数,求A和B的第k小数的函数,那么中位数的问题很容易解决。
求一个有序数组的第k个数只需要O(1)的复杂度,现在有两个数组,显然花费额外空间以O(n)时间归并然后O(1)寻找不满足题目要求。既然要求log时间复杂度,一般需要使用到二分思想。
分别考虑A和B的第k/2个元素:
- 如果它们相等,则第k个数为其中的任意一个
- 如果A中的比较大,则B中前k/2个元素都不可能是第k个数了,因为这个数至少应该为A的第k/2个数,把B的前k/2去掉,然后重新寻找。
- 如果B中的比较大,则把A的前k/2个数去掉,重新寻找。
直到A和B中某个变为空时或者寻找第1个数时可以停止递归,直接找到结果。
注意,上面的k/2只是理想的简单情况,实际上A和B的长度可能不够k/2,或者k为奇数等,但这些不是主要问题,可以让A取第k/2个数字,然后A不够长,则取A的最后一个数字,然后B取剩下长度对应的那个数字,具体参考代码。
(2)pseudo code
1 | double findMedianSortedArrays(vector<int>& nums1, vector<int>& nums2) { |
2、subproblem reduction graph
3、prove the correctness
寻找两个有序数组的第K大数,那么肯定是第1个数组贡献 a 个数,第二个数组贡献 K - a 个数。首先我们假设每个数组分别贡献 K/2个数,讨论 A[k/2] 和 B[k/2] 的大小情况。
当 A[k/2] > B[K/2] 时,很显然需要减少数组A贡献的数字的个数,增加数组B贡献的数组的个数,如下图:
当 A[k/2] < B[K/2] 时,很显然需要增加数组A贡献的数字的个数,减少数组B贡献的数组的个数,如下图:
随着算法的执行,搜索的数组长度不断缩小,最后一定会返回对应的中位数值。
4、the complexity of this algorithm
分析可得:
五、Divide and Conquer
Recall the problem of finding the number of inversions. As in the course, we are given a sequence of n numbers a1,··· ,an, which we assume are all distinct, and we define an inversion to be a pair i < j such that ai > aj.
We motivated the problem of counting inversions as a good measure of how different two orderings are. However, one might feel that this measure is too sensitive. Let’s call a pair a significant inversion if i < j and ai > 3aj. Given an O(nlogn) algorithm to count the number of significant inversions between two orderings.
1、problem-solving ideas and pseudo-code
(1)problem-solving ideas
求数列的逆序数,除了暴力求解方式外,也可以使用归并排序、树状数组、线段树等结构进行计算。此处选择使用归并排序。
首先,将数组从中间切开,分为左右两半,A[0…n/2] 和 A [n/2+1…n],分别计算这两个子问题的显著逆序数;
然后,计算跨左右两边的数对所形成的显著逆序的个数。
最后,将这三者的显著逆序数求和,即为整个数组的显著逆序数。
(2)pseudo code
1 | //统计变量num为全局变量 |
2、subproblem reduction graph
先分:
后合:
3、prove the correctness
其中,计算左右两边的显著逆序数的时候,会同时将左右两边的子数组变成有序。在计算跨两边的数对所形成的显著逆序的时候,就不用进行暴力遍历,只需O(n)的遍历即可。当 L[i] > 3*R[j] 时,RC += |L|-i 即可,L中位于下标 i 之后的数字就不用遍历了,自然要比 3*R[j] 大。
同时,计算过程是基于归并排序的,整个算法即正确。
4、the complexity of this algorithm
计算左边的时间复杂度为 T(n/2), 计算右边的时间复杂度为 T(n/2), 合并的时间复杂度为 O(n),所以总的时间复杂度为:
六、Divide and Conquer
Given a table M consisting of 2n ∗ 2n blocks, we want to fill it with a L-shaped module (consisting of three blocks). The L-shaped module is shown below.
Please give a fill method, so that the last element of the table (M2n,2n) is empty.
For example:
1、problem-solving ideas and pseudo-code
(1)problem-solving ideas
有一个特殊方格的棋盘覆盖问题,只不过,此题目将特殊方格定死了,只能为最右下角那个,即 P(size-1,size-1)那个小方格。
该题关键在于如何划分各L型骨牌所在位置区域。我们发现,L型骨牌占三个方格,我们可以把棋盘从中央分为四块,那么这四块子棋盘中仅一块是有特殊方格的,可以用一块骨牌使得其他三块子棋盘均被覆盖。以此为原则,无论这种分法是否最终可解,我们首先保证了每个子棋盘都有一个特殊方格,所以,分治的模型就出来了。
我们可以用递归来完成分治的任务。每次递归,chess_board(int posx,int posy,int x,int y,int size),(posx,posy)为子棋盘左上角坐标,size为子棋盘大小,因为棋盘总为正方形,所以size为边长,那么这3个参数就确定了子棋盘的位置和大小;(x,y)表示子棋盘中特殊方格的位置,这个位置是由上层递归分配骨牌后决定的。以此为标准,递归流程为:
①判断边界,若当前棋盘大小为1,则无法再分割,递归结束。
②定子棋盘中心位置。
③判断特殊方格所在位置(左上,右上,左下,或右下)。
④根据特殊方格位置确定所选L型骨牌,原特殊方格和三个L型骨牌的方格分别为四个子棋盘的特殊方格。
⑤依据④中判断,按编号填充棋盘。
⑥4次递归,分别对应四个子棋盘。
(2)pseudo code
1 | void chess_board(int posx,int posy,int x,int y,int size){ |
2、subproblem reduction graph
假设有如下图的一个棋盘,棋盘中有一个特殊方格:
第一次分割:
第二次分割:
第三次分割:
第四次分割:
最后解的形式如下图所示(右下角空):
3、prove the correctness
使用数学归纳法:
(1)k = 1 时, 有解
(2)k = 2 时,有解:
(3)设 k-1 时成立(k>2),将 2^k 2^k 棋盘分割为 4 个 2^(k-1) 2^(k-1) 子棋盘,如下图所示:
特殊方格必位于 4 个较小子棋盘之一中,其余 3 个子棋盘中无特殊方格。为了将这 3 个无特殊方格的子棋盘转化为特殊盘,我们可以用一个 L 型骨牌覆盖这 3 个较小的棋盘的汇合处,如下图所示,这 3 个子棋盘上被 L 型骨牌覆盖的方格就成为该棋盘上的特殊方格,从而将原问题化为 4 个较小规模的棋盘覆盖问题。递归的使用这种分割,直至棋盘简化为 2^1x2^1 棋盘。
综上可得,该算法正确,最终可求得想要的结果。
4、the complexity of this algorithm
算法的时间复杂度递推式如下:
即:
七、OJ第一题(寻找第K大数)
1 |
|
八、OJ第二题(二维最近点对)
1 |
|