华为od-矩阵匹配

题目描述

从一个 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;
    }

}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值