题目描述:
从一个N*M(N<=M)的矩阵中选出N个数,任意两个数字不能在同一行或同一列,求选出来的N个数中第K大的数字的最小值是多少。
输入描述:
输入矩阵要求:1<=K<=N<=M<=150
输入格式:N M K
N*M矩阵
输出描述:
N*M的矩阵中可以选出M!/N!种组合数组,每个组合数组中第K大的数中的最小值。无需考虑重复数字,直接取字典排序结果即可。
补充说明:注意:结果是第K大的数字的最小值
示例1
输入:3 4 2
1 5 6 6
8 3 4 3
6 8 6 3
输出:3
说明:N*M的矩阵中可以选出M!/N!种组合数组,每个组合数组中第K大的数中的最小值;上述输入中选出的数组组合为1,3,6; 1,3,3; 1,4,8; 1,4,3;......上述输入样例中选出的组合数组有24种,最小数组为1,3,3,则2大的最小值为3
解题思路:这道题目要求从一个 N x M 的矩阵中选出 N 个数,每个数来自不同的行和列,找出这些数中第 K 大的数的最小可能值。
步骤:
1. 问题理解:
需要从矩阵中选择 N 个数,这些数不能来自同一行或同一列。
在选出的数中,找出第 K 大的数,并使这个数尽可能小。
2. 核心思路:
使用二分查找法来确定满足条件的数值范围,结合匈牙利算法来验证当前的选择是否可行。
3. 二分查找:
设二分查找的范围为矩阵中的最小值(l)和最大值(r)。
对于一个给定的中间值 mid,检查是否可以选择 N 个数,使得这 N 个数中至少有 N - K + 1 个数小于等于 mid。如果可以,那么就说明在这个 mid 及其以下的数中可以找到合适的第 K 大的数。否则,mid 太小,不可能满足要求。
4. 匈牙利算法的使用:
匈牙利算法用于解决二分图的最大匹配问题。在这个问题中,矩阵的行和列可以看作是二分图的两个部分。我们需要检查是否存在一个匹配,使得每个选中的数不在同一行或同一列。
我们构建了一个匹配关系,判断在当前的 mid 值下,是否存在这样的匹配,使得能选出的数达到 N - K + 1 个。
5. 算法流程:
初始设定 l 为矩阵中的最小值,r 为矩阵中的最大值。
进行二分查找:
计算 mid 值。
使用 check 函数判断是否存在一个匹配,使得在当前 mid 下至少有 N - K + 1 个数可以被选中。
如果 check 返回 true,说明满足条件,缩小范围至 [l, mid]。
否则,调整范围至 [mid + 1, r]。
最后,l 的值就是要求的最小的第 K 大的数。
Java代码:
import java.util.Scanner;
import java.util.Arrays;
public class Main {
private static int[][] matrix;
private static int n, m, k;
private static boolean[] visited;
private static int[] match;
// 查找增广路径
private static boolean dfs(int x, int v) {
for (int i = 1; i <= m; i++) {
if (matrix[x][i] <= v) { // 如果当前的匹配值小于我们查询的v
if (visited[i]) continue; // 如果已经访问过经过 i 的增广路径
visited[i] = true;
if (match[i] == 0 || dfs(match[i], v)) { // 如果当前节点 i 没有匹配过,或者可以从 i 点找到增广路径
match[i] = x; // 记录该匹配
return true;
}
}
}
return false;
}
// 二分查找的判断函数
private static boolean check(int v) {
int count = 0;
Arrays.fill(match, 0); // 初始化所有的匹配都为空
for (int i = 1; i <= n; i++) { // 以行遍历,查询最大匹配
Arrays.fill(visited, false); // 初始化增广路径的查找点为空
if (dfs(i, v)) count++; // 找到一条增光路径
if (count >= n - k + 1) return true; // 当前的最大匹配已经大于 n-k+1
}
return false;
}
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
n = scanner.nextInt();
m = scanner.nextInt();
k = scanner.nextInt();
matrix = new int[n + 1][m + 1];
visited = new boolean[m + 1];
match = new int[m + 1];
int l = Integer.MAX_VALUE, r = Integer.MIN_VALUE;
// 读取矩阵并初始化二分查找的边界
for (int i = 1; i <= n; i++) {
for (int j = 1; j <= m; j++) {
matrix[i][j] = scanner.nextInt();
if (l > matrix[i][j]) l = matrix[i][j];
if (r < matrix[i][j]) r = matrix[i][j];
}
}
// 利用二分查找
while (l < r) {
int mid = (l + r) / 2;
if (check(mid)) {
r = mid; // 如果说目前的 <= mid 条件下 可以找到 n-k+1 个最大匹配那么最大匹配的右端点则在这个范围内
} else {
l = mid + 1; // 否则说明 mid 太小,查询左端点要加1
}
}
System.out.println(l); // 输出我们的 l
}
}