您现在的位置是:首页 > 技术教程 正文

华为OD机试 - 矩阵匹配(Java & JS & Python & C & C++)

admin 阅读: 2024-03-20
后台-插件-广告管理-内容页头部广告(手机)

题目描述

从一个 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
输出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个元素,这个N元素的特点是:任意两个不能同行同列。

而满足上面条件的N个元素存在多组,我们需要找到着各个组中第K大元素的最小值。

难点一:如何从矩阵中找到N个互相不同行同列的元素呢?

暴力枚举的话,肯定会超时,因此需要寻找更优解法。

根据要求,每行每列只能有一个元素被选择,即可以认为每个行号只能和一个列号进行配对,且配对过的列号不能再和其他行号配对,而形成了配对关系的行号,列号,其实就是一个元素的坐标位置。

因此,找N个互相不同行同列的元素,其实就是在二分图(所有行号一部分,所有列号一部分)找N个边的匹配。

如下图所示

关于二分图的知识可以看下:

HDU - 2063 过山车(Java & JS & Python & C)-CSDN博客

看完上面博客后,我们就可以继续后面说明了。

现在我们已经有了二分图了,也就可以找到具有N个边的"匹配",但是这种"匹配"可能非常多,难道要全部找出来,然后对比每个"匹配"中第K大,那不还是暴力吗?

题目需要我们多组N个元素中的第K大元素的最小取值,

换位思考一下,假设我们已经知道了第K大的最小取值是kth,那么:

  • 检查矩阵中是否至多找到(N - K + 1 个) ≤ kth 的元素值,且这些元素值互不同行同列

N个数中,有K-1个数比kth大,那么相对应的有 (N - (K-1)) = (N - K + 1 ) 个数 ≤ kth。

即找的 N - K + 1 个数中包含了 kth(第K大值)本身。

而kth的大小和二分图最大匹配是正相关的,因为:

每个匹配边 其实就是 行号到列号的配对连线

而行号和列号的组合其实就是坐标位置,根据坐标位置可以得到一个矩阵元素值

因此kth越小,意味着可以找到的 ≤ kth 的矩阵元素越少,相反的,kth 越大,则找到的 ≤ kth 的矩阵元素越多。

因此kth值大小和二分图最大匹配数是线性关系,我们可以使用二分法,来枚举kth。

二分枚举的范围是:1 ~ 矩阵元素最大值,这里不用担心二分枚举到kth不是矩阵元素,因为这种情况会被过滤掉,原因是:我们要找 N - K + 1 个 <= kth 的矩阵元素,最后把关的必然是 kth 本身,即我们必然要在矩阵中找到一个 kth 值,如果二分枚举到的 kth 不是矩阵元素,则无法满足这个要求。

二分枚举到一个kth值:

  • 如果kth使得二分图最大匹配 >= N-K+1 个,则说明当前kth取大了,我们应该尝试更小的kth值,即缩小二分右边界为kth-1
  • 如果kth使得二分图最大匹配 < N-K+1 个,则说明当前kth取小了,我们应该继续尝试更大的kth值,即扩大二分左边界为kth+1

当二分左右边界重合时的kth值即为题解。

关于二分法,可以看下:

算法设计 - 二分法和三分法,洛谷P3382_二分法与三分法-CSDN博客

JS算法源码

  1. const rl = require("readline").createInterface({ input: process.stdin });
  2. var iter = rl[Symbol.asyncIterator]();
  3. const readline = async () => (await iter.next()).value;
  4. void (async function () {
  5. const [n, m, k] = (await readline()).split(" ").map(Number);
  6. let min = 1;
  7. let max = -Infinity;
  8. const matrix = [];
  9. for (let i = 0; i < n; i++) {
  10. matrix.push((await readline()).split(" ").map(Number));
  11. max = Math.max(max, ...matrix[i]);
  12. }
  13. // 二分枚举第K大的最小取值
  14. while (min <= max) {
  15. // mid就是被枚举出来的N个数中的第K大的最小取值
  16. const mid = (min + max) >> 1;
  17. // 检查mid作为N个数中第K大值时,是否存在N-K+1个<=它的值
  18. if (check(mid)) {
  19. max = mid - 1;
  20. } else {
  21. min = mid + 1;
  22. }
  23. }
  24. console.log(min);
  25. function check(kth) {
  26. // 利用二分图最大匹配来求解,小于等于kth(第K大值)的元素个数(即二分图最大匹配)
  27. let smallerCount = 0;
  28. // 记录每个列号的匹配成功的行号
  29. // 初始时每个列号都处于未配对状态,此时将列号配对的行号赋值为-1
  30. const match = new Array(m).fill(-1);
  31. // 遍历行号,每个行号对互相心仪的列号发起配对请求
  32. for (let i = 0; i < n; i++) {
  33. // 记录增广路访问过的列号
  34. const vis = new Array(m).fill(false);
  35. if (dfs(i, kth, match, vis)) {
  36. smallerCount++;
  37. }
  38. }
  39. return smallerCount >= n - k + 1;
  40. }
  41. function dfs(i, kth, match, vis) {
  42. // 行号 i 发起了配对请求
  43. // 遍历每一个列号j
  44. for (let j = 0; j < m; j++) {
  45. // 如果当前列号j未被增广路探索过 && 当前列j行i可以配对(如果行列号位置(i,j)对应矩阵元素值小于等于kth(第K大值),则可以配对)
  46. if (!vis[j] && matrix[i][j] <= kth) {
  47. vis[j] = true;
  48. // 如果对应列号j未配对,或者,已配对但是配对的行号match[j]可以找到其他列号重新配对
  49. if (match[j] == -1 || dfs(match[j], kth, match, vis)) {
  50. // 则当前行号i 和 列号j 可以配对
  51. match[j] = i;
  52. return true;
  53. }
  54. }
  55. }
  56. return false;
  57. }
  58. })();

Java算法源码

  1. import java.util.Arrays;
  2. import java.util.Scanner;
  3. public class Main {
  4. static int n;
  5. static int m;
  6. static int k;
  7. static int[][] matrix;
  8. public static void main(String[] args) {
  9. Scanner sc = new Scanner(System.in);
  10. n = sc.nextInt();
  11. m = sc.nextInt();
  12. k = sc.nextInt();
  13. int min = 1;
  14. int max = Integer.MIN_VALUE;
  15. matrix = new int[n][m];
  16. for (int i = 0; i < n; i++) {
  17. for (int j = 0; j < m; j++) {
  18. matrix[i][j] = sc.nextInt();
  19. max = Math.max(max, matrix[i][j]);
  20. }
  21. }
  22. // 二分枚举第K大值
  23. while (min <= max) {
  24. // mid就是被枚举出来的N个数中的第K大值
  25. int mid = (min + max) >> 1;
  26. // 检查mid作为N个数中第K大值时,是否存在N-K+1个不大于它的值
  27. if (check(mid)) {
  28. max = mid - 1;
  29. } else {
  30. min = mid + 1;
  31. }
  32. }
  33. System.out.println(min);
  34. }
  35. public static boolean check(int kth) {
  36. // 利用二分图最大匹配来求解,小于等于kth(第K大值)的元素个数(即二分图最大匹配)
  37. int smallerCount = 0;
  38. // 记录每个列号的匹配成功的行号
  39. int[] match = new int[m];
  40. // 初始时每个列号都处于未配对状态,此时将列号配对的行号赋值为-1
  41. Arrays.fill(match, -1);
  42. // 遍历行号,每个行号对互相心仪的列号发起配对请求
  43. for (int i = 0; i < n; i++) {
  44. // 记录增广路访问过的列号
  45. boolean[] vis = new boolean[m];
  46. if (dfs(i, kth, match, vis)) smallerCount++;
  47. }
  48. return smallerCount >= n - k + 1;
  49. }
  50. public static boolean dfs(int i, int kth, int[] match, boolean[] vis) {
  51. // 行号 i 发起了配对请求
  52. // 遍历每一个列号j
  53. for (int j = 0; j < m; j++) {
  54. // 如果当前列号j未被增广路探索过 && 当前列j行i可以配对(如果行列号位置(i,j)对应矩阵元素值小于等于kth(第K大值),则可以配对)
  55. if (!vis[j] && matrix[i][j] <= kth) {
  56. vis[j] = true;
  57. // 如果对应列号j未配对,或者,已配对但是配对的行号match[j]可以找到其他列号重新配对
  58. if (match[j] == -1 || dfs(match[j], kth, match, vis)) {
  59. // 则当前行号i 和 列号j 可以配对
  60. match[j] = i;
  61. return true;
  62. }
  63. }
  64. }
  65. return false;
  66. }
  67. }

Python算法源码

  1. import sys
  2. # 输入获取
  3. n, m, k = map(int, input().split())
  4. matrix = [list(map(int, input().split())) for _ in range(n)]
  5. def dfs(i, kth, match, vis):
  6. # 行号 i 发起了配对请求
  7. # 遍历每一个列号j
  8. for j in range(m):
  9. # 如果当前列号j未被增广路探索过 && 当前列j行i可以配对(如果行列号位置(i,j)对应矩阵元素值小于等于kth(第K大值),则可以配对)
  10. if not vis[j] and matrix[i][j] <= kth:
  11. vis[j] = True
  12. # 如果对应列号j未配对,或者,已配对但是配对的行号match[j]可以找到其他列号重新配对
  13. if match[j] == -1 or dfs(match[j], kth, match, vis):
  14. # 则当前行号i 和 列号j 可以配对
  15. match[j] = i
  16. return True
  17. return False
  18. def check(kth):
  19. # 利用二分图最大匹配来求解,小于等于kth(第K大值)的元素个数(即二分图最大匹配)
  20. smallerCount = 0
  21. # 记录每个列号的匹配成功的行号
  22. # 初始时每个列号都处于未配对状态,此时将列号配对的行号赋值为-1
  23. match = [-1] * m
  24. # 遍历行号,每个行号对互相心仪的列号发起配对请求
  25. for i in range(n):
  26. # 记录增广路访问过的列号
  27. vis = [False] * m
  28. if dfs(i, kth, match, vis):
  29. smallerCount += 1
  30. return smallerCount >= n - k + 1
  31. # 算法入口
  32. def getResult():
  33. low = 1
  34. high = -sys.maxsize
  35. for i in range(n):
  36. for j in range(m):
  37. high = max(high, matrix[i][j])
  38. # 二分枚举第K大值
  39. while low <= high:
  40. # mid就是被枚举出来的N个数中的第K大值
  41. mid = (low + high) >> 1
  42. # 检查mid作为N个数中第K大值时,是否存在N-K+1个<=它的值
  43. if check(mid):
  44. high = mid - 1
  45. else:
  46. low = mid + 1
  47. return low
  48. # 算法调用
  49. print(getResult())

C算法源码

  1. #include
  2. #include
  3. #include
  4. #define MAX_SIZE 150
  5. #define bool int
  6. #define TRUE 1
  7. #define FALSE 0
  8. int n, m, k;
  9. int matrix[MAX_SIZE][MAX_SIZE];
  10. bool dfs(int i, int kth, int match[], int vis[]) {
  11. // 行号 i 发起了配对请求
  12. // 遍历每一个列号j
  13. for (int j = 0; j < m; j++) {
  14. // 如果当前列号j未被增广路探索过 && 当前列j行i可以配对(如果行列号位置(i,j)对应矩阵元素值小于等于kth(第K大值),则可以配对)
  15. if (!vis[j] && matrix[i][j] <= kth) {
  16. vis[j] = TRUE;
  17. // 如果对应列号j未配对,或者,已配对但是配对的行号match[j]可以找到其他列号重新配对
  18. if (match[j] == -1 || dfs(match[j], kth, match, vis)) {
  19. // 则当前行号i 和 列号j 可以配对
  20. match[j] = i;
  21. return TRUE;
  22. }
  23. }
  24. }
  25. return FALSE;
  26. }
  27. bool check(int kth) {
  28. // 利用二分图最大匹配来求解,小于等于kth(第K大值)的元素个数(即二分图最大匹配)
  29. int smallerCount = 0;
  30. // 记录每个列号的匹配成功的行号
  31. int match[m];
  32. // 初始时每个列号都处于未配对状态,此时将列号配对的行号赋值为-1
  33. for (int i = 0; i < m; i++) match[i] = -1;
  34. // 遍历行号,每个行号对互相心仪的列号发起配对请求
  35. for (int i = 0; i < n; i++) {
  36. // 记录增广路访问过的列号
  37. int vis[MAX_SIZE] = {FALSE};
  38. if (dfs(i, kth, match, vis)) {
  39. smallerCount++;
  40. }
  41. }
  42. return smallerCount >= (n - k + 1);
  43. }
  44. int main() {
  45. scanf("%d %d %d", &n, &m, &k);
  46. int min = 1;
  47. int max = INT_MIN;
  48. for (int i = 0; i < n; i++) {
  49. for (int j = 0; j < m; j++) {
  50. scanf("%d", &matrix[i][j]);
  51. max = (int) fmax(max, matrix[i][j]);
  52. }
  53. }
  54. // 二分枚举第K大值
  55. while (min <= max) {
  56. // mid就是被枚举出来的N个数中的第K大值
  57. int mid = (min + max) >> 1;
  58. // 检查mid作为N个数中第K大值时,是否存在N-K+1个<=它的值
  59. if (check(mid)) {
  60. max = mid - 1;
  61. } else {
  62. min = mid + 1;
  63. }
  64. }
  65. printf("%d\n", min);
  66. return 0;
  67. }

C++算法源码

  1. #include
  2. using namespace std;
  3. #define MAX_SIZE 150
  4. int n, m, k;
  5. int matrix[MAX_SIZE][MAX_SIZE];
  6. bool dfs(int i, int kth, int match[], bool vis[]) {
  7. // 行号 i 发起了配对请求
  8. // 遍历每一个列号j
  9. for (int j = 0; j < m; j++) {
  10. // 如果当前列号j未被增广路探索过 && 当前列j行i可以配对(如果行列号位置(i,j)对应矩阵元素值小于等于kth(第K大值),则可以配对)
  11. if (!vis[j] && matrix[i][j] <= kth) {
  12. vis[j] = true;
  13. // 如果对应列号j未配对,或者,已配对但是配对的行号match[j]可以找到其他列号重新配对
  14. if (match[j] == -1 || dfs(match[j], kth, match, vis)) {
  15. // 则当前行号i 和 列号j 可以配对
  16. match[j] = i;
  17. return true;
  18. }
  19. }
  20. }
  21. return false;
  22. }
  23. bool check(int kth) {
  24. // 利用二分图最大匹配来求解,小于等于kth(第K大值)的元素个数(即二分图最大匹配)
  25. int smallerCount = 0;
  26. // 记录每个列号的匹配成功的行号
  27. int match[m];
  28. // 初始时每个列号都处于未配对状态,此时将列号配对的行号赋值为-1
  29. for (int i = 0; i < m; i++) match[i] = -1;
  30. // 遍历行号,每个行号对互相心仪的列号发起配对请求
  31. for (int i = 0; i < n; i++) {
  32. // 记录增广路访问过的列号
  33. bool vis[MAX_SIZE] = {false};
  34. if (dfs(i, kth, match, vis)) smallerCount++;
  35. }
  36. return smallerCount >= n - k + 1;
  37. }
  38. int main() {
  39. cin >> n >> m >> k;
  40. int low = 1;
  41. int high = INT_MIN;
  42. for (int i = 0; i < n; i++) {
  43. for (int j = 0; j < m; j++) {
  44. cin >> matrix[i][j];
  45. high = max(high, matrix[i][j]);
  46. }
  47. }
  48. // 二分枚举第K大值
  49. while (low <= high) {
  50. // mid就是被枚举出来的N个数中的第K大值
  51. int mid = (low + high) >> 1;
  52. // 检查mid作为N个数中第K大值时,是否存在N-K+1个不大于它的值
  53. if (check(mid)) {
  54. high = mid - 1;
  55. } else {
  56. low = mid + 1;
  57. }
  58. }
  59. cout << low << endl;
  60. return 0;
  61. }

标签:
声明

1.本站遵循行业规范,任何转载的稿件都会明确标注作者和来源;2.本站的原创文章,请转载时务必注明文章作者和来源,不尊重原创的行为我们将追究责任;3.作者投稿可能会经我们编辑修改或补充。

在线投稿:投稿 站长QQ:1888636

后台-插件-广告管理-内容页尾部广告(手机)
关注我们

扫一扫关注我们,了解最新精彩内容

搜索