这是一个面试问题。
找到一个有排序的行和列的矩阵中第K小的元素。 是否正确,第K小的元素是像
找到一个有排序的行和列的矩阵中第K小的元素。 是否正确,第K小的元素是像
a[i, j]
这样的元素,满足i + j = K
?a[i, j]
这样的元素,满足i + j = K
?错误。
考虑一个简单的矩阵,例如这个:
1 3 5
2 4 6
7 8 9
9是最大(第9小)的元素。但是9在A [3,3]处,而3 + 3!= 9。(无论使用什么索引约定,都不可能成立)。
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个最小元素。O(k log(k))
解决方案。
构建一个最小堆。
将 (0,0)
添加到堆中。当我们还没有找到第 k
小的元素时,从堆中删除顶部元素 (x,y)
,并添加下一组还未被访问过的两个元素 [(x+1,y)
和 (x,y+1)]
。
我们在大小为 O(k)
的堆上执行了 O(k)
次操作,因此复杂度为 O(k log(k))
。
这个问题可以使用二分查找和优化计数在已排序矩阵中解决。二分查找需要O(log(n))的时间,并且对于每个搜索值,它平均需要n次迭代才能找到小于所搜索单个数的数字。二分查找的搜索空间被限制在矩阵中最小值mat[0][0]
和最大值mat[n-1][n-1]
之间。
对于从二分查找中选择的每个数字,我们需要计算小于或等于该特定数字的数字。因此,第个最小数字可以被找到。
为更好地理解,您可以参考此视频:
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;
}
正如之前提到的,最简单的方法是构建一个小根堆
。这里是使用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;
}
矩阵中第K小的元素:
该问题可以通过以下方式缩小范围。
如果k为20,则取k*k的矩阵(答案一定位于其中)。
现在,您可以反复将行成对合并以构建排序数组,然后找到第K个最小的数字。
//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];
}