LeetCode LCP 04. 覆盖【二分图最大匹配,匈牙利算法】困难

本文属于「征服LeetCode」系列文章之一,这一系列正式开始于2021/08/12。由于LeetCode上部分题目有锁,本系列将至少持续到刷完所有无锁题之日为止;由于LeetCode还在不断地创建新题,本系列的终止日期可能是永远。在这一系列刷题文章中,我不仅会讲解多种解题思路及其优化,还会用多种编程语言实现题解,涉及到通用解法时更将归纳总结出相应的算法模板。

为了方便在PC上运行调试、分享代码文件,我还建立了相关的仓库:https://github.com/memcpy0/LeetCode-Conquest。在这一仓库中,你不仅可以看到LeetCode原题链接、题解代码、题解文章链接、同类题目归纳、通用解法总结等,还可以看到原题出现频率和相关企业等重要信息。如果有其他优选题解,还可以一同分享给他人。

由于本系列文章的内容随时可能发生更新变动,欢迎关注和收藏征服LeetCode系列文章目录一文以作备忘。

你有一块棋盘,棋盘上有一些格子已经坏掉了。你还有无穷块大小为1 * 2的多米诺骨牌,你想把这些骨牌不重叠地覆盖在完好的格子上,请找出你最多能在棋盘上放多少块骨牌?这些骨牌可以横着或者竖着放。

输入:n, m 代表棋盘的大小;broken是一个b * 2的二维数组,其中每个元素代表棋盘上每一个坏掉的格子的位置。

输出:一个整数,代表最多能在棋盘上放的骨牌数。

示例 1:

输入:n = 2, m = 3, broken = [[1, 0], [1, 1]]
输出:2
解释:我们最多可以放两块骨牌:[[0, 0], [0, 1]]以及[[0, 2], [1, 2]]。(见下图)


示例 2:

输入:n = 3, m = 3, broken = []
输出:4
解释:下图是其中一种可行的摆放方式


限制:

  1. 1 <= n <= 8
  2. 1 <= m <= 8
  3. 0 <= b <= n * m

解法 匈牙利算法

看了标签才做出来,事后诸葛亮还能看出一些使用二分图的迹象:

  • 骨牌是1x2的大小,一个骨牌的两个位置一定是相邻的两个位置,这两个位置的下标之和的奇偶性相反。这就抽象成了一个二分图
  • 把奇数点看做男士,偶数点看做女士,建图后就比较直观。
  • ==要放置最多的多米诺骨牌,也就是找到一种方式,使得二分图中奇数点和偶数点连起来的边数量最大 ==,这就是典型的二分图最大匹配

因此,这题就是自行建图后使用匈牙利算法的模板题。

以示例一为例建图:

二分图最大匹配问题,一般可以用匈牙利算法解决。在介绍匈牙利算法之前,需要明确一些专有名词:

  • 匹配集合:我们最终的目标是最大化边的数量,这些边将加入匹配集合
  • 匹配边、匹配点:在二分图中,如果本次将两个点连成的边加入匹配集合,就说我们当前将这条边作为了匹配边,边的两个端点均称作匹配点。
  • 未匹配边、未匹配点:在二分图中,如果一个点有一条以上的边,并且其中某一条边已经被加入了匹配集合成为了匹配边,那么剩余的边均称作未匹配边,这些边的另一个端点称为未匹配点。
  • 增广路:以未匹配边开始和结束,且未匹配边与匹配边交替出现的路径。

为了便于大家理解,通过下图(红框和篮框分别表示二分图中的两部分,黑圆表示不同的点。黄和绿线都表示点之间的边)来解释上面 4 4 4 个概念:
300

  • 首先将 1 1 1 号点和 5 5 5 号点之间的边放入匹配集合,该边就变成了匹配边(黄色标识)。 1 1 1 5 5 5 号点就均变为了匹配点,此时,这两个点连接的其他边 ( 1 , 7 ) , ( 2 , 5 ) , ( 4 , 5 ) (1,7),(2,5),(4,5) (1,7),(2,5),(4,5) 就称作未匹配边,对应的点 2 , 4 , 7 2, 4, 7 2,4,7 就均称作未匹配点。
  • 其次将 3 3 3 号点和 6 6 6 号点之间的边放入匹配集合,该边就变成了匹配边,这两个点变为了匹配点。由于这两个点没有连其他的边,所以不会出现新的未匹配边
  • 此时发现,路径 2 − 5 − 1 − 7 2-5-1-7 2517 就是一条 未匹配边-匹配边-未匹配边 组合的增广路径。

明确了这些概念后,看匈牙利算法:

  1. 初始时,最大匹配集合为空。
  2. 我们先找到一组匹配边,加入匹配集合。
  3. 如果找到一条增广路径,就将其中的所有匹配边变为未匹配边,将所有的未匹配边变为匹配边。
  4. 循环步骤 3 3 3 ,直到图中不存在增广路径。算法结束。

匈牙利算法中,最重要的便是步骤 3 3 3 。深入理解——对于一条增广路径,根据其定义,必定含有 k + 1 k + 1 k+1 条未匹配边以及 k k k 条匹配边。那么,步骤 3 3 3 的作用,其实就是将未匹配边和匹配边互换,这样,==该路径上就会更新为 k k k 条未匹配边以及 k + 1 k + 1 k+1 条匹配边,匹配边的数量就比互换之前多了 1 1 1 ==。
500
结合刚才的图片来看:我们将增广路径 2 − 5 − 1 − 7 2-5-1-7 2517 上的未匹配边 ( 2 , 5 ) , ( 1 , 7 ) (2,5),(1,7) (2,5),(1,7) 变为匹配边,将匹配边 ( 5 , 1 ) (5,1) (5,1) 变为未匹配边,图中总匹配边数就从原来的两条 ( 1 , 5 ) , ( 3 , 6 ) (1,5),(3,6) (1,5),(3,6) 变成了三条 ( 2 , 5 ) , ( 1 , 7 ) , ( 3 , 6 ) (2, 5), (1, 7), (3, 6) (2,5),(1,7),(3,6)


一开始建二分图时,我们需要将题目给定的图标识成二分图(比如一部分标识为 0 0 0 ,另一部分标识为 1 1 1 )。但在本题中,棋盘上第 i i i 行第 j j j 列属于哪一部分可以直接根据 i + j i+j i+j 的奇偶性得到。

特别地,在二分图中,只需要从一个集合向另一个集合连有向边即可,不需要双向连边(虽然代码中随手写的双向连边)。另外,本题中棋盘上有些点不可以放多米诺骨牌,在连边过程中进行特判即可。

class Solution { 
public:
    int domino(int n, int m, vector<vector<int>>& broken) {
        vector<int> g[100];
        bool vis[100] = {false};
        bool b[100] = {false};
        int match[100];
        int ans = 0; 
        function<bool(int)> dfs = [&](int u) -> bool {
            for (int v : g[u]) { 
                if (!vis[v]) { // 没访问过
                    vis[v] = true; // 避免重复访问, 能让就让
                    if (match[v] == -1 || dfs(match[v])) {
                        match[v] = u; return true;
                    }
                }  
            }
            return false;
        };
        for (vector<int> &bv : broken) // 哪些位置破损 
            b[bv[0] * m + bv[1]] = true; 
        for (int i = 0; i < n; ++i) {
            for (int j = 0; j < m; ++j) {
                int u = i * m + j;
                if (b[u]) continue;
                int v1 = i * m + j + 1, v2 = (i + 1) * m + j;
                if (j + 1 < m && !b[v1]) { // 只存奇数点到偶数点的边也行
                    g[u].push_back(v1);
                    g[v1].push_back(u);
                }
                if (i + 1 < n && !b[v2]) { 
                    g[u].push_back(v2);
                    g[v2].push_back(u);
                }
            }
        }
        memset(match, -1, sizeof(match));
        for (int i = 0, t = n * m; i < t; ++i) {
            int x = i / m, y = i % m;
            memset(vis, false, size(vis));
            if (((x + y) & 1) && !b[i] && !vis[i] && dfs(i)) // 从奇数点出发向偶数点连边
                ++ans;
        }
        return ans;
    }
};

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/12558.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

[Java]Session机制

什么是Session Session是另一种记录客户状态的机制&#xff0c;不同的是Cookie保存在客户端浏览器中&#xff0c;而Session保存在服务器上。客户端浏览器访问服务器的时候&#xff0c;服务器把客户端信息以某种形式记录在服务器上。这就是Session。客户端浏览器再次访问时只需…

springboot整合redis

一、总体概述 1、redis配置文件 redis.conf配置文件&#xff0c;改完后确保生效&#xff0c;记得重启&#xff0c;记得重启 默认daemonize no 改为 daemonize yes 默认protected-mode yes 改为 protected-mode no 默认bind 127.0.0.1 改为 直接注释掉(默认bind 127.0.0.1只能…

ApplicationContextAware接口

一、ApplicationContextAware接口的基本介绍 public interface ApplicationContextAware extends Aware {void setApplicationContext(ApplicationContext applicationContext) throws BeansException;}在Spring/SpringMVC中&#xff0c;我们拿到IOC容器无非有三种方式&#x…

HCIE-Cloud Computing LAB常见问题收集谱

第一题&#xff1a;FusionCompute 扩容CNA与对接共享存储 FusionCompute&#xff1a;关联存储资源失败 物理阵列里面太多没清理的了。然后去排查问题&#xff0c;存储地址也正确&#xff0c;管理接口也互联&#xff0c;IQN号也修改了&#xff0c;结果是启动器快满了 排查网…

Javaweb基础配置模板(mybatis+javaweb)

1.大纲规划图 本配置涉及的技术:mybatis,javaweb,json转换&#xff0c;分页查询等 2.导入相关的配置文件pom.xml 2.1 依赖文件 <dependencies> <!-- 测试依赖--><dependency><groupId>junit</groupId><artifactId>junit</artifact…

如何把视频里的声音提取出来,4种有效方法学起来

在我们日常生活中&#xff0c;可能会有需要从视频文件中提取音频的情况&#xff0c;比如想要将视频中的歌曲或语音内容提取出来&#xff0c;或者电脑上看视频时&#xff0c;总有一些很有意思的BGM&#xff0c;想录下来或者提取出来单独使用&#xff0c;不过有些小伙伴可能不知道…

[abc复盘] abc297 20230409

[atc复盘] abc297 20230409 一、本周周赛总结A - Double Click1. 题目描述2. 思路分析3. 代码实现 B - chess9601. 题目描述2. 思路分析3. 代码实现 C - PC on the Table1. 题目描述2. 思路分析3. 代码实现 D - Count Subtractions1. 题目描述2. 思路分析3. 代码实现 E - Kth …

AOP通知中获取数据

AOP通知中获取数据 之前我们写AOP仅仅是在原始方法前后追加一些操作&#xff0c;接下来我们要说说AOP中数据相关的内容&#xff0c;我们将从获取参数、获取返回值和获取异常三个方面来研究切入点的相关信息。 获取切入点方法的参数&#xff1a;所有的通知类型都可以获取参数 …

【JSP学习笔记】3.JSP 指令及动作元素

前言 本章介绍JSP的指令和动作元素。 JSP 指令 JSP指令用来设置整个JSP页面相关的属性&#xff0c;如网页的编码方式和脚本语言。 语法格式如下&#xff1a; <% directive attribute"value" %>指令可以有很多个属性&#xff0c;它们以键值对的形式存在&am…

数云融合|新手入门,5分钟秒懂开源

目录 一、开源软件开源领域的两大组织&#xff1a;FSF和OSI 二、开源许可证开源意味着免费吗&#xff1f; 三、开源技术应用领域四、总结 一、开源软件 开源即开放源代码&#xff0c;他的核心是源代码公开&#xff0c;任何人都可以查看、使用、修改和分发。与之相对的是闭源&a…

技术+商业“双轮”驱动,量旋科技加速推进全方位的量子计算解决方案

【中国&#xff0c;深圳】4月14日&#xff0c;在第三个“世界量子日”&#xff0c;以“‘双轮’驱动 加速未来”为主题的量旋科技2023战略发布会在线上举办。 本次发布会&#xff0c;量旋科技全线升级了三大业务线产品&#xff1a;其中重点布局的超导量子计算体系产品&#xf…

DolphinScheduler×T3出行 | 打造车联网一站式数据应用交互体验

点击蓝字 关注我们 用户案例 | T3 出行 业务挑战 作为一家车联网驱动的公司&#xff0c;T3出行汇聚了“人、车、路、云”各端的海量数据。为了承载如此多元化的数据以更好地释放数据价值&#xff0c;T3出行构建了以Apache Hudi为基础的企业级的数据湖&#xff0c;并在此之上搭建…

1 分钟给 Siri 升个级!从智Z变身 ChatSiri!

原文链接&#xff1a;https://forum.laf.run/d/79/17 众所周知&#xff0c;Siri 是一个智 Z&#xff01;那么如果能接入大火的 chatGPT&#xff0c;是不是就会从智 Z 变成人工智能&#xff1f;&#xff01; 众所周知&#xff0c;Laf 是一个集函数、数据库、存储为一体的云开发…

第五章_Redis事务

是什么 官网 能做什么 一个队列中&#xff0c;一次性、顺序性、排他性的执行一系列命令 Redis事务 VS 数据库事务 1 单独的隔离操作 Redis的事务仅仅是保证事务里的操作会被连续独占的执行&#xff0c;redis命令执行是单线程架构&#xff0c;在执行完事务内所有指令前是不可…

消息队列kafka及zookeeper机制

目录 一、zookeeper 1、zookeeper简介 2、zookeeper特点 3、zookeeper工作模式及机制 4、zookeeper应用场景及选举机制 5、zookeeper集群部署 ①实验环境 ②安装zookeeper 二、消息队列kafka 1、为什么要有消息队列 2、使用消息队列的好处 3、kafka简介 4、kafka…

【云原生】Kubernetes(k8s)之容器的探测

Kubernetes&#xff08;k8s&#xff09;之容器的探测 一、探测类型及使用场景1.1、startupProbe&#xff08;启动探测&#xff09;1.2、readinessProbe&#xff08;就绪探测&#xff09;1.3、livenessProbe&#xff08;存活探测&#xff09; 二、检查机制三、探测结果四、容器探…

MySQL - 基于SSL安全连接的主从复制

目录 &#x1f341;主从复制的原理 &#x1f341;部署master &#x1f341;部署slave &#x1f341;测试SSL主从复制 &#x1f990;博客主页&#xff1a;大虾好吃吗的博客 &#x1f990;MySQL专栏&#xff1a;MySQL专栏地址 生产环境中一台mysql主机存在单点故障&#xff0c;所…

SWCF QA集锦待查收 (车联网与V2X、自动驾驶、5G毫米波、射频测试、频谱监测与规划等)

感谢大家的观看与支持&#xff01;我们为大家整理了本次发布会中的演讲资料&#xff0c;汇总了直播过程中的热点问题并请讲师进行了详细解答&#xff0c;在此整理分享给大家&#xff01; 演讲Q&A Q&#xff1a;目前5G天线支持最大的MIMO是多少&#xff1f; A&#xff1a;…

计算机:理解操作系统:内存篇(上)

内存篇 1. 什么是内存2. C/C内存模型2.1 代码段和数据段2.2 堆和栈 本节是操作系统系列教程的第三篇文章&#xff0c;属于操作系统第一章即基础篇&#xff0c;在真正开始操作系统相关章节前在这一部分回顾一些重要的主题&#xff0c;算是温故知新吧&#xff0c;以下是目录&…

ICMP隧道技术实现防火墙穿透

1.在mac os的虚拟机里准备三台kali 三台主机ip地址分别是 192.168.1.15&#xff0c;192.168.1.16&#xff0c;192.168.1.17&#xff0c; 为方便描述 依次把他们暂且命名为主机A,主机B,主机C 2.在主机C 上打开终端&#xff0c;输入 cd /usr/local/src 然后新建一个hello.txt 文…