所以,你有n个排序的数组(不一定长度相等),你要返回组合数组中的第k个最小元素(即合并所有n个排序数组形成的组合数组)
我现在已经尝试了它和它的其他变种已经有一段时间了,直到现在我只觉得有两个相等长度的数组,两个都是有序的,一个必须返回这两个的中位数。 这具有对数时间复杂度。
在此之后,我尝试将其概括为在两个排序的数组中找到第k个最小的。 Here是关于SO的问题。 即使在这里,给出的解决方案对我来说并不明显。但即使我以某种方式设法说服自己这个解决方案,我仍然很好奇如何解决绝对一般情况(这是我的问题)
有人可以解释我一步一步的解决方案(我认为应该再采用对数时间,即O(log(n 1 )+ log(n 2 ) ... + log(n N )其中n 1 ,n 2 ... n N 是n个数组的长度)从更具体的情况开始,然后转移到更一般的情况?
我知道类似的问题对于更具体的案例都存在于互联网上,但我没有找到令人信服的明确答案。
Here 是关于SO的问题(及其答案)的链接,它处理5个排序的数组并找到组合数组的中位数。答案对我来说太复杂了,无法概括它。
对于更具体的案例(正如我在帖子中提到的),即使是干净的方法也是受欢迎的。
PS:你认为这可以进一步推广到未排序的数组吗?
PPS:这不是一个家庭作业问题,我只是准备面试。答案 0 :(得分:12)
这并没有概括链接,但确实解决了问题:
当你到达每个数组只有一个元素(或0)的点时,用这些数据创建一个大小为n的新数组,排序并选择第k个元素。
因为你总是保证删除一个数组的至少一半,所以在 N 迭代中,你将摆脱一半的元素。这意味着有 N log k 迭代。每次迭代都是 N log k 的顺序(由于二进制搜索),所以整个事情是 N ^ 2(log k)^ 2 当然,这都是最坏的情况,基于这样的假设,你只能摆脱一半的最大阵列,而不是其他阵列。在实践中,我认为典型的性能会比最坏的情况好一些。
答案 1 :(得分:2)
不能在O(n)
时间内完成。 Proof Sketch 如果确实如此,则必须完全不看至少一个数组。显然,一个数组可以任意改变kth
元素的值。
我有一个相对简单的O(n*log(n)*log(m))
,其中m
是最长数组的长度。我确信它可能会稍微快一些,但速度要快得多。
考虑一个简单的情况,你有n
个数组,每个数组的长度为1.显然,这与查找长度为k
的未排序列表中的n
元素是同构的。可以在O(n)
中找到它,请参阅Median of Medians algorithm, originally by Blum, Floyd, Pratt, Rivest and Tarjan,并且不能(渐近地)更快的算法。
现在的问题是如何将其扩展为更长的排序数组。这是算法:找出每个数组的中位数。对元组(median,length of array/2)
列表进行排序,并按中位数对其进行排序。通过保持长度的总和,直到达到大于k的总和。你现在有一对中位数,这样你就知道第k个元素就在它们之间。现在对于每个中位数,我们知道kth是否大于或小于它,因此我们可以丢弃每个数组的一半。重复。一旦数组都是一个元素长(或更少),我们使用选择算法。
实现这一点将揭示额外的复杂性和边缘条件,但没有任何东西会增加渐近的复杂性。每一步
O(1)
,所以O(n)
总计O(n log n)
O(n)
O(1)
数组O(n)
进行切片,O(n) + O(n log n) + O(n) + O(n) = O(n log n)
总计即log m
。并且,我们必须执行此操作,直到最长的数组长度为1,这将需要O(n*log(n)*log(m))
步骤,总共O(m)
你问这是否可以归结为未排序数组的情况。可悲的是,答案是否定的。考虑我们只有一个数组的情况,那么最佳算法必须至少与每个元素进行一次比较,总共O(m)
。如果n个未排序数组的解决方案更快,那么我们可以通过将单个数组拆分为n个部分来实现选择。由于我们刚刚证明选择是{{1}},我们被困住了。
答案 2 :(得分:1)
您可以查看我最近对相关问题here的回答。相同的想法可以推广到多个数组而不是2.在每次迭代中,如果k小于所有数组的中间索引之和,则可以拒绝具有最大中间元素的数组的后半部分。或者,如果k大于所有数组的中间索引之和,则可以拒绝具有最小中间元素的数组的前半部分,调整k。继续这样做,直到你有一个数组减少到0的长度。答案是最后一个数组的第k个元素,它没有被剥离为0个元素。
运行时分析:
在每次迭代中,你摆脱了一个数组的一半。但要确定哪个阵列将减少,您需要花时间线性数组。假设每个数组的长度相同,运行时间为c c log(n),其中c是数组的数量,n是每个数组的长度。
答案 3 :(得分:0)
如果k不是那么大,我们可以保持优先级最小队列。然后循环排序数组的每个头以获得最小元素和队列。当队列的大小是k时。我们得到最小的k。
也许我们可以将n排序的数组视为存储桶,然后尝试使用存储桶排序方法。
答案 4 :(得分:0)
这可以被认为是合并排序的后半部分。我们可以简单地将所有排序列表合并到一个列表中......但只保留合并列表中的k个元素从合并到合并。这具有仅使用O(k)空间的优点,但是比合并排序的O(n log n)复杂度稍微好一些。也就是说,它实际上应该比合并排序稍快一些。从最终组合列表中选择第k个最小值是O(1)。这种复杂性并不是那么糟糕。
答案 5 :(得分:0)
存在一个在O(N log k)时间内解决问题的概括,请参阅question here。
答案 6 :(得分:0)
旧问题,但没有一个答案足够好。因此,我将使用滑动窗口技术和堆发布解决方案:
class Node {
int elementIndex;
int arrayIndex;
public Node(int elementIndex, int arrayIndex) {
super();
this.elementIndex = elementIndex;
this.arrayIndex = arrayIndex;
}
}
public class KthSmallestInMSortedArrays {
public int findKthSmallest(List<Integer[]> lists, int k) {
int ans = 0;
PriorityQueue<Node> pq = new PriorityQueue<>((a, b) -> {
return lists.get(a.arrayIndex)[a.elementIndex] -
lists.get(b.arrayIndex)[b.elementIndex];
});
for (int i = 0; i < lists.size(); i++) {
Integer[] arr = lists.get(i);
if (arr != null) {
Node n = new Node(0, i);
pq.add(n);
}
}
int count = 0;
while (!pq.isEmpty()) {
Node curr = pq.poll();
ans = lists.get(curr.arrayIndex)[curr.elementIndex];
if (++count == k) {
break;
}
curr.elementIndex++;
pq.offer(curr);
}
return ans;
}
}
我们在这里需要访问的最大元素数是O(K)
,并且有M
个数组。因此有效时间复杂度将为O(K*log(M))
。
答案 7 :(得分:0)
可以通过在每个数组中进行二分搜索来完成,同时计算较小元素的数量。
我使用 bisect_left
和 bisect_right
使其也适用于非唯一数字,
from bisect import bisect_left
from bisect import bisect_right
def kthOfPiles(givenPiles, k, count):
'''
Perform binary search for kth element in multiple sorted list
parameters
==========
givenPiles are list of sorted list
count is the total number of
k is the target index in range [0..count-1]
'''
begins = [0 for pile in givenPiles]
ends = [len(pile) for pile in givenPiles]
#print('finding k=', k, 'count=', count)
for pileidx,pivotpile in enumerate(givenPiles):
while begins[pileidx] < ends[pileidx]:
mid = (begins[pileidx]+ends[pileidx])>>1
midval = pivotpile[mid]
smaller_count = 0
smaller_right_count = 0
for pile in givenPiles:
smaller_count += bisect_left(pile,midval)
smaller_right_count += bisect_right(pile,midval)
#print('check midval', midval,smaller_count,k,smaller_right_count)
if smaller_count <= k and k < smaller_right_count:
return midval
elif smaller_count > k:
ends[pileidx] = mid
else:
begins[pileidx] = mid+1
return -1
答案 8 :(得分:0)
这就是代码。 O(k*log(m))
public int findKSmallest(int[][] A, int k) {
PriorityQueue<int[]> queue = new PriorityQueue<>(Comparator.comparingInt(x -> A[x[0]][x[1]]));
for (int i = 0; i < A.length; i++)
queue.offer(new int[] { i, 0 });
int ans = 0;
while (!queue.isEmpty() && --k >= 0) {
int[] el = queue.poll();
ans = A[el[0]][el[1]];
if (el[1] < A[el[0]].length - 1) {
el[1]++;
queue.offer(el);
}
}
return ans;
}
答案 9 :(得分:-1)
请找到下面的C#代码,找到两个排序数组联合中第k个最小元素。时间复杂度:O(logk)
public int findKthElement(int k, int[] array1, int start1, int end1, int[] array2, int start2, int end2)
{
// if (k>m+n) exception
if (k == 0)
{
return Math.Min(array1[start1], array2[start2]);
}
if (start1 == end1)
{
return array2[k];
}
if (start2 == end2)
{
return array1[k];
}
int mid = k / 2;
int sub1 = Math.Min(mid, end1 - start1);
int sub2 = Math.Min(mid, end2 - start2);
if (array1[start1 + sub1] < array2[start2 + sub2])
{
return findKthElement(k - mid, array1, start1 + sub1, end1, array2, start2, end2);
}
else
{
return findKthElement(k - mid, array1, start1, end1, array2, start2 + sub2, end2);
}
}