题目描述
从一个 N * M(N ≤ M)的矩阵中选出 N 个数,任意两个数字不能在同一行或同一列,求选出来的 N 个数中第 K 大的数字的最小值是多少。
输入描述
输入矩阵要求:1 ≤ K ≤ N ≤ M ≤ 150
输入格式:
N M K
N*M矩阵
输出描述
N*M 的矩阵中可以选出 M! / N! 种组合数组,每个组合数组种第 K 大的数中的最小值。无需考虑重复数字,直接取字典排序结果即可。
注意:结果是第 K 大的数字的最小值
用例
输入
3 4 2
1 5 6 6
8 3 4 3
6 8 6 3
输出
N*M的矩阵中可以选出 M!/ N!种组合数组,每个组合数组种第 K 大的数中的最小值;
上述输入中选出数组组合为:
1,3,6;
1,3,3;
1,4,8;
1,4,3;
…
上述输入样例中选出的组合数组有24种,最小数组为1,3,3,则第2大的最小值为3
思路:二分法+匈牙利算法(二分图匹配)
1、假设第K个大的值为 value , 判断能否找到 n-k+1 行 不同列 的值都比value小的序列,如果有,则缩小value范围,否则扩大
2、遍历每一行
2.1、遍历每一列,如果这个列还没有被访问并且值 小于等于 <= value,则
-
标记这个列位置已经访问
-
如果这个列未被匹配过,则匹配;如果这个列已经匹配了某个行并且这个行能找到另外一列满足 值小于等于vaule并且未被访问过,则将这个列匹配当前行。
2.2 匹配成功1列,则计数+1
3、判断匹配的行数是否 >=n-k+1 ,满足 则 二分边界向左移;否则向右移
4、通常左边界就是最终结果,但是本道题比较特殊,当l==r 退出循环时,l未必满足条件,因此需要再次验证左边界是否满足条件。
import java.util.*;
public class Main {
static int N;
static int M;
static int K;
public static void main(String[] args) {
Scanner in = new Scanner(System.in);
int[] input = Arrays.stream(in.nextLine().split(" ")).mapToInt(Integer::valueOf).toArray();
N=input[0]; //行
M=input[1];//列
K=input[2];//目标第K个
int[][] grid=new int[N][M];//矩阵图
int l=Integer.MAX_VALUE; //二分法的左边界
int r=Integer.MIN_VALUE; //二分法的右边界
for (int i=0;i<N;i++){
for (int j=0;j<M;j++){
int data = in.nextInt();
grid[i][j]=data;
l=Math.min(l,data);
r=Math.max(r,data);
}
}
int min=r; //用来保留符合条件的最小值,默认是右边界
//本题目的二分法比较特殊,通常二分法的【左边界l】会作为最终结果,但是这道题最终的l可能会不满足,因此二分法结束后需要再次验证l
while (l<r){
int mid=(l+r)/2;
//符合条件,继续寻找更小的值
if (dfs(grid,mid)){
min=r; //保留符合条件的最小值
r=mid;
}else {
l=mid+1;
}
}
//当l==r时,l未必就是最终解,需要再次验证l
if (dfs(grid,l)){
System.out.println(l);
}else {
System.out.println(min);
}
}
//二部图匹配
public static boolean dfs(int[][] grid,int target){
int[] colMatch=new int[M];
Arrays.fill(colMatch,-1);
int res=0;
//行匹配列
for (int row=0;row<N;row++){
boolean[] used=new boolean[M];
if (half(row,grid,used,colMatch,target)){
res++;
}
}
// 如果我们希望第K个数尽可能小,就必须找到至少 N-K+1个不超过target的数
// 假设第K个数最小为2(或者更小,题目要求找到最小的) ,N=5 那么必须找到 5-2+1=4 个不超过2的数, 2 2 2 2 5 , 1 1 1 2 5 , 1 1 1 1 5
return res>=N-K+1;
}
/**
二部图匹配算法固定的4个参数:
* @param row 当前要匹配的行
* @param grid 矩阵图
* @param used 列的访问数组
* @param colMatch 列的匹配结果
* @param target 不能超过的目标值
* @return
*/
public static boolean half(int row,int[][] grid,boolean[] used,int[] colMatch,int target){
for (int col=0;col<M;col++){
//该行列的值不超过target,并且当前列没有访问过
if (grid[row][col]<=target&&!used[col]){
used[col]=true;
//当前列没有和任何行匹配, 或者当前列匹配的 那【行】,那【行】还能再和其他列匹配,则把当前列匹配给当前行row
if (colMatch[col]==-1 || half(colMatch[col],grid,used,colMatch,target)){
colMatch[col]=row;
return true;
}
}
}
return false;
}
}

被折叠的 条评论
为什么被折叠?



