给定一个 n x n 矩阵,其中每行和每列元素均按升序排序,找到矩阵中第 k 小的元素。 请注意,它是排序后的第 k 小元素,而不是第 k 个不同的元素。 示例: matrix = [ [ 1, 5, 9], [10, 11, 13], [12, 13, 15] ], k = 8, 返回 13。
分析: i)利用矩阵的特点:其中每行和每列元素均按升序排序。 由这个特点可以得到几个性质: 1.A是四个元素中最小值,D是最大值。 2.如果C>n,则D一定大于n,此时如果A<n,则B小于n。也就是说,当我们判断完C大于n后,想要找到下一个小于等于n的元素,应该先判断A的情况;同理,判断完C小于等于n后,应该先判断D的情况。 3.C小于等于n时,C所在的那一列,C以上的元素都是小于等于n的。
根据上面的总结从一个矩阵的左下角元素开始,按照2的规则: 如果当前元素≤n,则把这一列中到当前元素的个数进行累加,然后判断右边的元素; 如果当前元素>n,就判断上边的元素; 一直遍历到右上角,就可以得到矩阵中所有小于等于n的元素个数。
ii)利用小于等于mid的元素个数进行二分查找 把矩阵想象成下图的序列:最小值和最大值是确定的,中间不是具体的元素,而是一个范围。 当序列中小于等于mid的元素个数小于k时,则第k小的元素肯定在[mid+1,right]中找到; 当序列中小于等于mid的元素个数大于等于k时,则第k小的元素肯定在[left,mid]中找到;
在二分查找中需要注意“=”到底归在哪边的问题: 当left=i,right=i+1时,mid=left ①num<mid, right = mid-1; num≥mid, left=mid。 这时,如果num<mid, right= left-1,两个边界不会相交,无法得到正确结果;如果num≥mid,left= left,会陷入死循环。 ②num≤mid, right = mid; num>mid, left=mid+1。 这时,如果num<mid, right= left;如果num≥mid,left= right,无论是哪种情况,left和right都会相交。
class Solution { public: int kthSmallest(vector<vector<int>>& matrix, int k) { int left = matrix[0][0],right = matrix[matrix.size()-1][matrix[0].size()-1]; int mid; while(left<right){ mid = left+(right-left)/2;//防止溢出 if(lessThanK(matrix,k,mid)) left = mid+1; else right= mid; } return left; } bool lessThanK(vector<vector<int>>& matrix, int k,int mid){ int row = matrix.size()-1,col = 0; int sum = 0; while(row>=0&&col<matrix[0].size()){ if(matrix[row][col]<=mid){ sum+= row+1; col++; }else row--; } if(sum<k)return true; else return false; } };