矩阵匹配-Java(D)

题目描述:

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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

ai因思坦

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值