这是一个面试问题。
在具有已排序行和列的矩阵中找到K th 最小元素
K th 最小元素是a[i, j]
之一,例如i + j = K
,这是正确的吗?
答案 0 :(得分:33)
假。
考虑一个像这样的简单矩阵:
1 3 5
2 4 6
7 8 9
9是最大的(第9个最小的)元素。但是9是在A [3,3]和3 + 3!= 9.(无论你使用什么索引约定,都不可能是真的。)
你可以在O(k log n)时间内通过逐步合并行来解决这个问题,用堆扩充来有效地找到最小元素。
基本上,您将第一列的元素放入堆中并跟踪它们来自的行。在每一步中,从堆中删除最小元素并从它来自的行中推送下一个元素(如果到达行的末尾,则不要推送任何内容)。删除最小值和添加新元素都需要花费O(log n)。在第j步中,删除j
最小元素,因此在k
步之后,您将完成O(k log n)
次操作的总成本(其中n是矩阵中的行数) )。
对于上面的矩阵,您最初从堆中的1,2,7
开始。您删除1
并添加3
(因为第一行为1 3 5
)以获取2,3,7
。您删除2
并添加4
以获取3,4,7
。移除3
并添加5
以获取4,5,7
。移除4
并添加6
以获取5,6,7
。请注意,我们将按全局排序顺序删除元素。您可以看到,继续此过程将在k次迭代后产生k
个最小元素。
(如果矩阵的行数多于列数,则对列进行操作以减少运行时间。)
答案 1 :(得分:22)
O(k log(k))
解决方案。
建立一个minheap。
将(0,0)
添加到堆中。虽然我们没有找到kth
最小元素,但是从堆中删除顶部元素(x,y)
并添加下两个元素[(x+1,y)
和(x,y+1)]
(如果它们尚未访问过)之前。
我们正在对O(k)
大小的堆执行O(k)
操作,因此复杂性很高。
答案 2 :(得分:5)
可以使用二进制搜索和排序矩阵中的优化计数来解决此问题。二进制搜索要花费 O(log(n))时间,对于每个搜索值,平均要进行 n 次迭代才能找到小于所搜索数字的数字。二进制搜索的搜索空间限制为矩阵mat[0][0]
的最小值和最大值mat[n-1][n-1]
。
对于从二分搜索中选择的每个数字,我们需要计算小于或等于该特定数字的数字。因此,可以找到最小的数字。
为更好地理解您可以参考以下视频:
答案 3 :(得分:1)
从左上角开始遍历矩阵(0,0)并使用二进制堆来存储"边界" - 矩阵的访问部分与其余部分之间的边界。
Java实现:
private static class Cell implements Comparable<Cell> {
private final int x;
private final int y;
private final int value;
public Cell(int x, int y, int value) {
this.x = x;
this.y = y;
this.value = value;
}
@Override
public int compareTo(Cell that) {
return this.value - that.value;
}
}
private static int findMin(int[][] matrix, int k) {
int min = matrix[0][0];
PriorityQueue<Cell> frontier = new PriorityQueue<>();
frontier.add(new Cell(0, 0, min));
while (k > 1) {
Cell poll = frontier.remove();
if (poll.y + 1 < matrix[poll.x].length) frontier.add(new Cell(poll.x, poll.y + 1, matrix[poll.x][poll.y + 1]));
if (poll.x + 1 < matrix.length) frontier.add(new Cell(poll.x + 1, poll.y, matrix[poll.x + 1][poll.y]));
if (poll.value > min) {
min = poll.value;
k--;
}
}
return min;
}
答案 4 :(得分:1)
正如人们之前提到的,最简单的方法是建立一个min heap
。这是使用PriorityQueue的Java实现:
private int kthSmallestUsingHeap(int[][] matrix, int k) {
int n = matrix.length;
// This is not necessary since this is the default Int comparator behavior
Comparator<Integer> comparator = new Comparator<Integer>() {
@Override
public int compare(Integer o1, Integer o2) {
return o1 - o2;
}
};
// building a minHeap
PriorityQueue<Integer> pq = new PriorityQueue<>(n*n, comparator);
for (int i = 0; i < n; i++) {
for (int j = 0; j < n; j++) {
pq.add(matrix[i][j]);
}
}
int ans = -1;
// remove the min element k times
for (int i = 0; i < k; i++) {
ans = pq.poll();
}
return ans;
}
答案 5 :(得分:1)
上述解决方案无法处理对角线条件,因此无法应用于下方矩阵
int arr2[][] = { { 1, 4, 7, 11, 15 },
{ 2, 5, 8, 12, 19 },
{ 3, 6, 9, 16, 22 },
{ 10, 13, 14, 17, 24 },
{ 18, 21, 23, 26, 30 } }
并且k = 5
返回7而答案是5
答案 6 :(得分:0)
似乎这只是使用了这个功能:每一行都被排序,但不使用它的逐列排序功能。
答案 7 :(得分:0)
矩阵中的第K个最小元素:
问题可以缩小如下。
如果k是20,那么取k * k矩阵(答案肯定是谎言。)
现在,您可以重复合并行中的行以构建排序数组,然后找到第k个最小数字。
答案 8 :(得分:-1)
//int arr[][] = {{1, 5, 10, 14},
// {2, 7, 12, 16},
// {4, 10, 15, 20},
// {6, 13, 19, 22}
//};
// O(k) Solution
public static int myKthElement(int arr[][], int k) {
int lRow = 1;
int lCol = 0;
int rRow = 0;
int rCol = 1;
int count = 1;
int row = 0;
int col = 0;
if (k == 1) {
return arr[row][col];
}
int n = arr.length;
if (k > n * n) {
return -1;
}
while (count < k) {
count++;
if (arr[lRow][lCol] < arr[rRow][rCol]) {
row = lRow;
col = lCol;
if (lRow < n - 1) {
lRow++;
} else {
if (lCol < n - 1) {
lCol++;
}
if (rRow < n - 1) {
lRow = rRow + 1;
}
}
} else {
row = rRow;
col = rCol;
if (rCol < n - 1) {
rCol++;
} else {
if (rRow < n - 1) {
rRow++;
}
if (lCol < n - 1) {
rCol = lCol + 1;
}
}
}
}
return arr[row][col];
}