我在相关的主题上看到了很多这样的主题,但没有一个能提供有效的方法。
我想找到 k-th
二维阵列上的最小元素(或中值) [1..M][1..N]
其中每行按升序排序,所有元素都是不同的。
我想有 O(M log MN)
解决方案,但我不知道如何实现(中位数或使用线性复杂度的划分是一些方法,但没有更多的想法。
这是一个老的谷歌面试问题,可以在这里搜索。
但是现在我想要提示或描述最有效的算法(最快的算法)。
我也读了一篇关于这里的文章,但我不明白。
更新1:这里有一个解决方案,但当维度为奇数时。
5条答案
按热度按时间qgelzfjb1#
添加了另一个答案以提供实际的解决方案。这一个已经被留下,因为它是相当兔子洞的评论。
我相信最快的解决方案是k路合并算法。它是一个
O(N log K)
合并算法K
已排序的列表,总共N
将项目放入单个大小排序列表中N
.https://en.wikipedia.org/wiki/k-way_merge_algorithm#k-方式\u合并
给予
MxN
列表。结果是O(MNlog(M))
. 但是,这是为了对整个列表进行排序。既然你只需要第一个K
最小的项目而不是全部N*M
,性能为O(Klog(M))
. 这比你想要的要好一点O(K) <= O(M)
.虽然这假设你有
N
大小排序列表M
. 如果你真的有M
大小排序列表N
,这可以很容易地处理,尽管只需更改数据的循环方式(请参阅下面的伪代码),但这确实意味着性能会有所提高O(K log(N))
相反。k-way合并只是将每个列表的第一项添加到堆或具有
O(log N)
插入和O(log N)
找到心灵。k-way merge的伪代码如下所示:
对于每个排序的列表,将第一个值插入到数据结构中,并使用某种方法确定值来自哪个列表。ie:你可以插入
[value, row_index, col_index]
而不仅仅是value
. 这还可以让您轻松地处理列或行上的循环。从数据结构中删除最小值并附加到排序列表。
考虑到第2步中的项目来自列表
I
从列表中添加下一个最低值I
到数据结构。ie:如果值为row 5 col 4 (data[5][4])
. 如果使用行作为列表,那么下一个值是row 5 col 5 (data[5][5])
. 如果您使用的是列,那么下一个值是row 6 col 4 (data[6][4])
. 将下一个值插入到数据结构中,就像插入#1(即:[value, row_index, col_index]
)根据需要返回步骤2。
根据您的需要,请执行步骤2-4
K
次。idfiyjo82#
btilly和nuclearman的答案提供了两种不同的方法,一种是二进制搜索,另一种是k路行合并。
我的建议是把这两种方法结合起来。
如果k很小(比如说小于m乘以2或3)或很大(对于simmetry,接近nxm)enoug
vmpqdwk33#
所以要解决这个问题,需要解决一个稍微不同的问题。我们想知道每一行的上界/下界,总的第k个截止点在哪里。然后我们可以通过,验证在下界或下界的事物数是<k,在上界或下界的事物数是>k,并且它们之间只有一个值。
我想出了一个策略,可以同时在所有行中对这些边界进行二进制搜索。作为一个二进制搜索它“应该”采取
O(log(n))
通行证。每一关都涉及O(m)
总共工作了O(m log(n))
次。我应该加引号,因为我没有证据证明O(log(n))
通行证。事实上,在一行中可能过于激进,从其他行中发现选择的轴心点已关闭,然后不得不后退。但我相信它很少后退,实际上是O(m log(n))
.策略是跟踪每一行的下界、上界和中间值。每一次传递我们都会生成一个范围的加权序列,从下到中、从中到上、从上到尾,权重是其中的事物数,值是序列中的最后一个。然后我们在该数据结构中找到第k个值(按权重),并将其用作每个维度的二进制搜索的轴心。
如果轴从下到上超出了范围,我们可以通过在纠正错误的方向上加宽间隔来进行纠正。
当我们有了正确的顺序,我们就有了答案。
有很多边缘情况,所以关注完整的代码可能会有所帮助。
我还假设每行的所有元素都是不同的。如果他们不是,你可以进入无尽的循环(解决这意味着更多的边缘情况……)
50pmv0ei4#
也许我错过了什么,但如果你
NxM
矩阵A
有M
行已经升序排序,没有重复的元素k
-行的最小值只是拾取k
-行中的第个元素O(1)
. 要移动到2d,只需选择k
-改为按升序排序O(M.log(M))
再挑一次k-th
导致O(N.log(N))
.让我们有矩阵
A[N][M]
元素所在的位置A[column][row]
排序k-th
列A
提升O(M.log(M))
如此排序A[k][i]
哪里i = { 1,2,3,...M }
提升挑选
A[k][k]
结果呢如果你想在所有元素中取第k个最小值
A
相反,您需要以类似于merge sort的形式利用已经排序的行。创建空列表
c[]
等待k
最小值处理列
创建临时数组
b[]
它保存处理过的列O(N.log(N))
合并c[]
以及b[]
所以呢c[]
坚持到k
最小值使用临时数组
d[]
将导致O(k+n)
如果在合并过程中没有使用b
停止处理列这可以通过添加标志数组来完成
f
从哪来的b,c
该值是在合并过程中获取的,然后只是检查是否从中获取了任何值b
输出c[k-1]
当把所有这些放在一起时,最后的复杂性是O(min(k,M).N.log(N))
如果我们考虑一下k
小于M
我们可以改写成O(k.N.log(N))
否则O(M.N.log(N))
. 而且平均来说,要迭代的列的数量将更不可能~(1+(k/N))
所以平均复杂度是~O(N.log(N))
但这只是我的猜测,可能是错的。下面是小型c++/vcl示例:
忽略vcl的东西。函数生成计算
a0, a
矩阵,其中a0
完全分类和a
只对行进行排序,所有值都是不同的。函数kmin
上面描述的算法是否返回第k个最小值a[m][n]
为了分类,我用了这个:这里是输出:
这个例子只迭代了5列。。。
dzjeubhm5#
似乎最好的办法是在越来越大的区块中进行k-way合并。k-way合并试图构建一个排序列表,但是我们不需要对它进行排序,也不需要考虑每个元素。相反,我们将创建一个半排序的间隔。间隔将被排序,但仅按最高值排序。
https://en.wikipedia.org/wiki/k-way_merge_algorithm#k-方式\u合并
我们使用与k-way合并相同的方法,但是有一个扭曲。基本上,它的目的是间接地建立一个半排序的子列表。例如,它不是找到[1,2,3,4,5,6,7,8,10]来确定k=10,而是找到类似于[(1,3),(4,6),(7,15)]的东西。对于k-way合并,我们每次从每个列表中考虑一个项目。在这种方法中,当从给定的列表中提取时,我们首先要考虑z项,然后是2z项,然后是22z项,因此第i次要考虑2^iz项。给定一个mxn矩阵,这意味着我们需要
O(log(N))
列表中的项目M
次。对于每个排序的列表,插入第一个
K
使用某种方法确定值来自哪个列表,将子列表添加到数据结构中。我们希望数据结构使用插入其中的子列表中的最高值。在本例中,我们需要类似于[max\u value of sublist,row index,start\u index,end\u index]的内容。O(m)
从数据结构中删除最小的值(现在是一个值列表)并附加到排序列表。O(log (m))
考虑到第2步中的项目来自列表I
添加下一个2^i * Z
列表中的值I
在第i次从特定列表中提取数据结构时(基本上只是从数据结构中移除的子列表中出现的数字的两倍)。O(log m)
如果半排序子列表的大小大于k,则使用二进制搜索查找第k个值。O(log N))
. 如果数据结构中还有任何子列表,其中最小值小于k。转到步骤1,将列表作为输入,并使用新的K
存在k - (size of semi-sorted list)
.如果半排序子列表的大小等于k,则返回半排序子列表中的最后一个值,这是第k个值。
如果半排序子列表的大小小于k,请返回步骤2。
至于表现。让我们看看这里:
拿
O(m log m)
将初始值添加到数据结构。最多需要考虑一下
O(m)
每个子列表需要O(log n)
o(m log n)的时间。最后需要执行二进制搜索,
O(log m)
,如果k的值不确定(第4步),可能需要将问题简化为递归子列表,但我认为这不会影响大o。编辑:我相信这只是增加了另一个O(mlog(n))
在最坏的情况下,这对大o没有影响。看起来像是
O(mlog(m) + mlog(n))
或者只是O(mlog(mn))
.作为优化,如果k大于
NM/2
考虑最小值时考虑最大值,考虑最大值时考虑最小值。当k接近时,这将大大提高性能NM
.