Medium

题目描述

给你一个 n x n 矩阵 matrix,其中每行和每列元素均按升序排序,找到矩阵中第 k 小的元素。

请注意,它是 排序后 的第 k 小元素,而不是第 k不同 的元素。

你必须找到一种内存复杂度优于 O(n²) 的解法。

示例 1:

输入:matrix = [[1,5,9],[10,11,13],[12,13,15]], k = 8
输出:13
解释:矩阵中的元素为 [1,5,9,10,11,12,13,13,15],第 8 小元素是 13

示例 2:

输入:matrix = [[-5]], k = 1
输出:-5

提示:

  • n == matrix.length == matrix[i].length
  • 1 <= n <= 300
  • -10⁹ <= matrix[i][j] <= 10⁹
  • 题目数据 保证 matrix 中的所有行和列都按 非递减顺序 排列
  • 1 <= k <= n²

进阶:

  • 你能否用一个恒定的内存(即 O(1) 内存复杂度)来解决这个问题?
  • 你能在 O(n) 的时间复杂度下解决这个问题吗?这个方法对于面试来说可能太超前了,但是你会发现阅读这篇文章很有趣。

解题思路

本题有两种常见解法:

解法一:最小堆(优先队列)

利用矩阵行列有序的特性,用最小堆维护当前最小的k个候选元素。初始时将第一行元素加入堆,每次取出堆顶最小元素,同时将其下方元素(如果存在且未访问过)加入堆。重复k次后得到答案。

解法二:二分搜索(推荐)

关键观察:对于矩阵中任意值mid,我们可以在O(n)时间内统计出小于等于mid的元素个数。利用这个性质,在[matrix[0][0], matrix[n-1][n-1]]范围内二分搜索第k小的值。

对于统计小于等于mid的元素个数,从矩阵右上角开始遍历:

  • 如果当前元素 <= mid,说明该列从当前位置往下的所有元素都 <= mid,计数后向下移动
  • 如果当前元素 > mid,向左移动

这种方法的优势是空间复杂度仅O(1),且时间复杂度为O(n*log(max-min)),在大多数情况下比堆方法更优。

二分搜索法还有一个细节:最终答案必须是矩阵中实际存在的元素,这可以通过精确的二分边界控制来保证。

代码实现

class Solution {
public:
    int kthSmallest(vector<vector<int>>& matrix, int k) {
        int n = matrix.size();
        int left = matrix[0][0];
        int right = matrix[n-1][n-1];
        
        while (left < right) {
            int mid = left + (right - left) / 2;
            int count = countLessEqual(matrix, mid);
            
            if (count < k) {
                left = mid + 1;
            } else {
                right = mid;
            }
        }
        
        return left;
    }
    
private:
    int countLessEqual(vector<vector<int>>& matrix, int target) {
        int n = matrix.size();
        int count = 0;
        int row = 0, col = n - 1;
        
        while (row < n && col >= 0) {
            if (matrix[row][col] <= target) {
                count += col + 1;
                row++;
            } else {
                col--;
            }
        }
        
        return count;
    }
};
class Solution:
    def kthSmallest(self, matrix: List[List[int]], k: int) -> int:
        n = len(matrix)
        left, right = matrix[0][0], matrix[n-1][n-1]
        
        while left < right:
            mid = (left + right) // 2
            count = self.countLessEqual(matrix, mid)
            
            if count < k:
                left = mid + 1
            else:
                right = mid
                
        return left
    
    def countLessEqual(self, matrix: List[List[int]], target: int) -> int:
        n = len(matrix)
        count = 0
        row, col = 0, n - 1
        
        while row < n and col >= 0:
            if matrix[row][col] <= target:
                count += col + 1
                row += 1
            else:
                col -= 1
                
        return count
public class Solution {
    public int KthSmallest(int[][] matrix, int k) {
        int n = matrix.Length;
        int left = matrix[0][0];
        int right = matrix[n-1][n-1];
        
        while (left < right) {
            int mid = left + (right - left) / 2;
            int count = CountLessEqual(matrix, mid);
            
            if (count < k) {
                left = mid + 1;
            } else {
                right = mid;
            }
        }
        
        return left;
    }
    
    private int CountLessEqual(int[][] matrix, int target) {
        int n = matrix.Length;
        int count = 0;
        int row = 0, col = n - 1;
        
        while (row < n && col >= 0) {
            if (matrix[row][col] <= target) {
                count += col + 1;
                row++;
            } else {
                col--;
            }
        }
        
        return count;
    }
}
var kthSmallest = function(matrix, k) {
    const n = matrix.length;
    let left = matrix[0][0];
    let right = matrix[n-1][n-1];
    
    while (left < right) {
        const mid = Math.floor((left + right) / 2);
        const count = countLessEqual(matrix, mid);
        
        if (count < k) {
            left = mid + 1;
        } else {
            right = mid;
        }
    }
    
    return left;
};

function countLessEqual(matrix, target) {
    const n = matrix.length;
    let count = 0;
    let row = 0, col = n - 1;
    
    while (row < n && col >= 0) {
        if (matrix[row][col] <= target) {
            count += col + 1;
            row++;
        } else {
            col--;
        }
    }
    
    return count;
}

复杂度分析

解法时间复杂度空间复杂度
最小堆O(k log n)O(n)
二分搜索O(n log(max-min))O(1)

推荐解法: 二分搜索法,因为空间复杂度更优,且在大多数情况下时间复杂度也更好。

相关题目