Hard

题目描述

给你一个 m x n 的矩阵 mat,矩阵中的每一行都按非递减顺序排列,另给你一个整数 k

你可以从每一行中选择一个元素来构成一个数组。

返回所有可能数组中第 k 小的数组和。

示例 1:

输入:mat = [[1,3,11],[2,4,6]], k = 5
输出:7
解释:从每一行中选择一个元素,前k个最小的和为:
[1,2], [1,4], [3,2], [3,4], [1,6]。其中第5小的和是7。

示例 2:

输入:mat = [[1,3,11],[2,4,6]], k = 9
输出:17

示例 3:

输入:mat = [[1,10,10],[1,4,5],[2,3,6]], k = 7
输出:9
解释:从每一行中选择一个元素,前k个最小的和为:
[1,1,2], [1,1,3], [1,4,2], [1,4,3], [1,1,6], [1,5,2], [1,5,3]。其中第7小的和是9。

提示:

  • m == mat.length
  • n == mat.length[i]
  • 1 <= m, n <= 40
  • 1 <= mat[i][j] <= 5000
  • 1 <= k <= min(200, nm)
  • mat[i] 是一个非递减数组

解题思路

这道题要求在矩阵中每行选择一个元素,使得所有可能的数组和中第k小的值。我们可以使用优先队列(最小堆)来解决这个问题。

核心思路

  1. 使用最小堆来维护当前最小的数组和
  2. 初始状态:每行都选择第一个元素(最小值),计算初始和
  3. 每次从堆中取出最小和,然后生成下一批候选:对于当前选择的索引组合,尝试将每一行的索引向右移动一位
  4. 为了避免重复,使用集合来记录已经访问过的索引组合

具体步骤

  • 初始化堆,放入所有元素都选择第0列的组合
  • 重复k次:弹出最小和,将其对应的"下一个"组合加入堆中
  • “下一个"组合是指将某一行的列索引增加1得到的新组合

优化点

  • 由于每行已经排序,我们可以确保生成的候选组合是有序的
  • 使用集合避免重复访问同一组合
  • 时间复杂度主要取决于k和堆操作

代码实现

class Solution {
public:
    int kthSmallest(vector<vector<int>>& mat, int k) {
        int m = mat.size(), n = mat[0].size();
        
        // 优先队列存储 (sum, indices)
        priority_queue<pair<int, vector<int>>, vector<pair<int, vector<int>>>, greater<pair<int, vector<int>>>> pq;
        set<vector<int>> visited;
        
        // 初始状态:每行都选第一个元素
        vector<int> initial(m, 0);
        int initialSum = 0;
        for (int i = 0; i < m; i++) {
            initialSum += mat[i][0];
        }
        
        pq.push({initialSum, initial});
        visited.insert(initial);
        
        // 取k次最小值
        for (int cnt = 0; cnt < k; cnt++) {
            auto [currentSum, indices] = pq.top();
            pq.pop();
            
            if (cnt == k - 1) {
                return currentSum;
            }
            
            // 生成下一批候选
            for (int i = 0; i < m; i++) {
                if (indices[i] + 1 < n) {
                    vector<int> newIndices = indices;
                    newIndices[i]++;
                    
                    if (visited.find(newIndices) == visited.end()) {
                        visited.insert(newIndices);
                        int newSum = currentSum - mat[i][indices[i]] + mat[i][newIndices[i]];
                        pq.push({newSum, newIndices});
                    }
                }
            }
        }
        
        return -1;
    }
};
class Solution:
    def kthSmallest(self, mat: List[List[int]], k: int) -> int:
        import heapq
        
        m, n = len(mat), len(mat[0])
        
        # 优先队列存储 (sum, indices_tuple)
        pq = []
        visited = set()
        
        # 初始状态:每行都选第一个元素
        initial_indices = tuple([0] * m)
        initial_sum = sum(mat[i][0] for i in range(m))
        
        heapq.heappush(pq, (initial_sum, initial_indices))
        visited.add(initial_indices)
        
        # 取k次最小值
        for cnt in range(k):
            current_sum, indices = heapq.heappop(pq)
            
            if cnt == k - 1:
                return current_sum
            
            # 生成下一批候选
            for i in range(m):
                if indices[i] + 1 < n:
                    new_indices = list(indices)
                    new_indices[i] += 1
                    new_indices_tuple = tuple(new_indices)
                    
                    if new_indices_tuple not in visited:
                        visited.add(new_indices_tuple)
                        new_sum = current_sum - mat[i][indices[i]] + mat[i][new_indices[i]]
                        heapq.heappush(pq, (new_sum, new_indices_tuple))
        
        return -1
public class Solution {
    public int KthSmallest(int[][] mat, int k) {
        int m = mat.Length, n = mat[0].Length;
        
        var pq = new SortedSet<(int sum, string indices)>();
        var visited = new HashSet<string>();
        
        // 初始状态:每行都选第一个元素
        var initial = new int[m];
        for (int i = 0; i < m; i++) initial[i] = 0;
        
        int initialSum = 0;
        for (int i = 0; i < m; i++) {
            initialSum += mat[i][0];
        }
        
        string initialKey = string.Join(",", initial);
        pq.Add((initialSum, initialKey));
        visited.Add(initialKey);
        
        // 取k次最小值
        for (int cnt = 0; cnt < k; cnt++) {
            var (currentSum, indicesStr) = pq.Min;
            pq.Remove(pq.Min);
            
            if (cnt == k - 1) {
                return currentSum;
            }
            
            var indices = indicesStr.Split(',').Select(int.Parse).ToArray();
            
            // 生成下一批候选
            for (int i = 0; i < m; i++) {
                if (indices[i] + 1 < n) {
                    var newIndices = (int[])indices.Clone();
                    newIndices[i]++;
                    
                    string newKey = string.Join(",", newIndices);
                    if (!visited.Contains(newKey)) {
                        visited.Add(newKey);
                        int newSum = currentSum - mat[i][indices[i]] + mat[i][newIndices[i]];
                        pq.Add((newSum, newKey));
                    }
                }
            }
        }
        
        return -1;
    }
}
/**
 * @param {number[][]} mat
 * @param {number} k
 * @return {number}
 */
var kthSmallest = function(mat, k) {
    let current = [mat[0][0]];
    
    for (let i = 1; i < mat.length; i++) {
        const next = [];
        const minHeap = new MinPriorityQueue({ priority: x => x.sum });
        const visited = new Set();
        
        minHeap.enqueue({ sum: current[0] + mat[i][0], idx: 0 });
        visited.add(0);
        
        while (next.length < k && !minHeap.isEmpty()) {
            const { sum, idx } = minHeap.dequeue().element;
            next.push(sum);
            
            for (let j = 0; j < current.length; j++) {
                const newSum = current[j] + mat[i][idx];
                if (newSum === sum) {
                    if (idx + 1 < mat[i].length && !visited.has(idx + 1)) {
                        minHeap.enqueue({ sum: current[j] + mat[i][idx + 1], idx: idx + 1 });
                        visited.add(idx + 1);
                    }
                    break;
                }
            }
        }
        
        current = next;
    }
    
    return current[k - 1];
};

复杂度分析

复杂度类型复杂度说明
时间复杂度O(k × m × log(k × m))最多处理k×m个状态,每次堆操作为O(log(k×m))
空间复杂度O(k × m)优先队列和visited集合的空间开销