有序矩阵中第K小的元素
题干描述
IDEA
如果现在给定了矩阵中的某个数 x x x,如何快速找出在矩阵中小于等于 x x x的数有多少个呢?
-
如图所示,可以考虑从最后一行出发,向右寻找第一个大于 x x x的数,则这个数的左侧都是小于等于 x x x的数
-
在这个第一个大于 x x x的数向上走一步,到达上一行。显然此时该行该位置左侧的数都满足小于等于 x x x,但是右侧还可能具有小于等于 x x x的数,因为每一列自上到下是递增的。
- 因此需要从此时的位置向右重复上面的步骤。
-
在上述两步进行的同时计数
-
以上过程重复进行,直到到达第一行。由于对于每一行都找到了正确的结果,因此很容易得出最后结果的正确性。
-
因此考虑对区间[矩阵中的最小数,矩阵中的最大数]进行二分查找。
AC代码
class Solution {
public int kthSmallest(int[][] matrix, int k) {
int n = matrix[0].length;
int low = matrix[0][0];
int high = matrix[n - 1][n - 1];
while(low < high)
{
int mid = low + (((high - low))>>1);
int count = 0;
int i = 0; //当前列
int cur_row = n - 1; //当前行
while(cur_row>=0)
{
while(i<n && matrix[cur_row][i] <= mid)
i++; //在当前行往后走,直到碰到边界
//实质上拐弯的元素才是需要的
count+=i;
cur_row--;
}
if(count < k)
low = mid + 1;
else high = mid;
}
return high;
}
}
细节分析
- 上述的代码十分美丽,但具有许多值得考究的细节
- 这里mid = low + (((high - low))>>1),这和mid = (low + high)/2是等价的吗?
- 不等价!
- 例如low = -5,high = -4,前面式子计算结果为-5, 后面的计算结果为-4.说明/运算的计算结果是往0靠近的。
- 而使用mid = low + (((high - low))>>1)保证了最终的计算结果总是往low的方向靠近不论结果的正负
- 上述循环中的终止条件是什么(即返回的结果)?
- 显然 l o w > h i g h low>high low>high和 l o w = = h i g h low == high low==high是两个首选
- 事实上,只可能是
l
o
w
=
=
h
i
g
h
low == high
low==high
- 考察退出这个循环前的最后一次循环,high比low多1
- 此时由上面的分析,计算出来的 m i d mid mid是靠近low的,准确的说,在这最后一次循环中mid就等于low。
- 考察最后的赋值方式,无论最后是对于low赋值还是对于high赋值, 最后循环退出的条件就是low == high;
- 最重要的point:上述最后求出的high值(亦即Low值)一定存在于矩阵中吗?
- 答案是一定存在!
- 初始阶段,由于low指向矩阵中的最小元素,high指向矩阵中的最大元素,[low,high]区间中显然存在目标元素。
- 使用分类讨论证明下面结论:无论如何以[low,high]为区间的元素里面包含目标值。
- 假设某个时刻,low指向了我们要找的目标值,而high也等于我们要找的目标值时,结论显然成立。
- 假设某个时刻,low指向了我们要找的目标值,而high大于我们要找的目标值时,进入循环:那么求出的mid一定是大于等于我们找到的目标值的,所以count的值一定大于等于k,于是会执行语句high = mid,此时的[low,high]即将范围缩小的区间包含目标值。
- 假设在某个时刻,low小于我们要找的目标值,而high等于我们要找的目标值,进入循环。那么求出的mid一定是小于等于我们的目标值的。所以最后的count值一定会小于等于k。如果count的值小于k(即仅有小于k的元素满足小于等于mid),所以mid一定比目标值小,执行low = mid+1后[low,high]区间显然一定包含目标值。如果count的值等于k,说明刚好有k个元素满足小于等于mid,此时mid指向的值一定是我么要找的目标值,这是因为:
- 由于原先high指向我们的目标值,因此这里的mid一定小于等于目标值。
- mid不可能小于目标值。否则,按上述每行的统计过程,count一定小于k,这与count==k矛盾。
- 所以此时算法令high = mid,[low,high]区间中一定包含目标值,因为至少high是目标值。
- 假设在某个时刻,low小于我们要找的目标值,而high大于我们要找的目标值,进入循环。则根据求出的mid计算出来的count值所有情况都有可能满足。如果
c
o
u
n
t
<
k
count < k
count<k,则mid一定小于目标值,算法执行low = mid+1后显然[low,high]区间中包含目标值。而如果
c
o
u
n
t
≥
k
count \geq k
count≥k,分情况讨论下:
- 如果 c o u n t = = k count == k count==k,那么mid的值一定大于等于目标值。否则如果mid小于目标值按上述按行统计的方法一定有 c o u n t < k count < k count<k,矛盾。所以此时令high = mid,由于mid大于目标值,[low,high]区间中存在目标值。
- 如果 c o u n t > k count >k count>k,那么mid的值肯定是大于目标值的,否则 c o u n t < = k count<=k count<=k。所以此时令high = mid,由于mid大于目标值,最后求出的区间[low,high]中一定存在目标值。
- 可以看到,综上所述,正确性的关键在于按行统计小于等于目标值的方式。即遇到第一个大于目标值的数,该行统计停止。