Leetcode 2123. 使矩阵中的 1 互不相邻的最小操作数

news2025/6/5 1:55:51

1.题目基本信息

1.1.题目描述

给你一个 下标从 0 开始 的矩阵 grid。每次操作,你可以把 grid 中的 一个 1 变成 0 。

如果一个矩阵中,没有 1 与其它的 1 四连通(也就是说所有 1 在上下左右四个方向上不能与其他 1 相邻),那么该矩阵就是 完全独立 的。

请返回让 grid 成为 完全独立 的矩阵的 最小操作数。

1.2.题目地址

https://leetcode.cn/problems/minimum-operations-to-remove-adjacent-ones-in-matrix/description/

2.解题方法

2.1.解题思路

二分图+匈牙利算法求二分图的最大匹配数

二分图+Hopcroft-karp算法求二分图的最大匹配数

2.2.解题步骤

二分图+匈牙利算法步骤

  • 第一步,构建二分图。在所有值为1的结点中,其中偶数下标行的偶数下标结点和奇数下标行的奇数下标结点为左结点集合uSet,其余结点为右结点集合vSet;uSet和vSet中彼此相邻代表一条边

  • 第二步,执行匈牙利算法获取最大匹配数

    • 匈牙利算法的步骤请看代码注释

二分图+Hopcroft-karp算法步骤

  • 第一步,构建二分图。在所有值为1的结点中,其中偶数下标行的偶数下标结点和奇数下标行的奇数下标结点为左结点集合uSet,其余结点为右结点集合vSet;uSet和vSet中彼此相邻代表一条边

  • 第二步,执行二分图+Hopcroft-karp算法获取最大匹配数

    • Hopcroft-karp算法的步骤请看代码注释

3.解题代码

二分图+匈牙利算法代码

# ==> 匈牙利算法
# params:graph:二分图的邻接表;leftNodes:二分图左集合的所有结点数组
# return:返回二分图中的最大匹配数(等于最小点覆盖数)
from collections import defaultdict
def hungarian(graph:dict[int,list[int]], leftNodes:list[int]) -> int:
    # 第一步,构建维护变量。rightMatch维护右集合中各个结点的匹配状态
    rightMatch = {}
    # 第二步,构建dfs递归函数。递归任务:判断左集合中结点u是否能成功匹配。如果u相邻结点v没有匹配,则直接匹配;如果v已经匹配,则将v标记为已访问,dfs(match[v])观察是否能让match[v]结点匹配非v结点来释放v结点,让u进行匹配,即贪心地进行匹配。
    def dfs(u:int) -> bool:
        # 2.1.枚举u的相邻的右集合结点v
        for v in graph[u]:
            # 2.2.判断v是否已访问,已访问则跳过
            if not visited[v]:
                # 2.3.标记v为已访问,并根据条件判断是否能匹配,能匹配的情况下记录右结点v的匹配状态到rightMatch中
                visited[v] = True
                if v not in rightMatch or dfs(rightMatch[v]):
                    rightMatch[v] = u
                    return True
        # 2.4.如果最终都没能匹配一个,则返回False
        return False
    # 第三步,枚举每一个左集合结点u,调用dfs判断各个u结点是否能成功进行匹配,并记录最大匹配数到ans中
    ans = 0
    for u in leftNodes:
        visited = defaultdict(bool)
        if dfs(u):
            ans += 1
    return ans


class Solution:
    def getGraphAndNodes(self, grid: List[List[int]]):
        m, n = len(grid), len(grid[0])
        # 第一步,构建二分图。在所有值为1的结点中,其中偶数下标行的偶数下标结点和奇数下标行的奇数下标结点为左结点集合uSet,其余结点为右结点集合vSet;uSet和vSet中彼此相邻代表一条边
        graph = defaultdict(list)
        leftNodes, rightNodes = [], []
        for i in range(m):
            for j in range(n):
                if grid[i][j] == 1:
                    # 1.1.将结点添加到图中
                    node = i * n + j
                    if (i % 2 == 0 and j % 2 == 0) or (i % 2 == 1 and j % 2 == 1):
                        leftNodes.append(node)
                    else:
                        rightNodes.append(node)
                    graph[node] = []
                    # 1.2.将边添加到图中
                    for dr, dc in [[-1, 0], [0, -1], [1, 0], [0, 1]]:
                        nr, nc = i + dr, j + dc
                        if 0 <= nr < m and 0 <= nc < n and grid[nr][nc] == 1:
                            node2 = nr * n + nc
                            graph[node].append(node2)
        # print(graph)
        return graph, leftNodes, rightNodes

    # 思路1【超时】:二分图+匈牙利算法。题目转换:等价于求二分图的最大匹配数
    def minimumOperations(self, grid: List[List[int]]) -> int:
        graph, leftNodes, _ = self.getGraphAndNodes(grid)
        # 第二步,执行匈牙利算法获取最大匹配数
        return hungarian(graph, leftNodes)

二分图+Hopcroft-karp算法代码

# ==> 匈牙利算法
# params:graph:二分图的邻接表;leftNodes:二分图左集合的所有结点数组
# return:返回二分图中的最大匹配数(等于最小点覆盖数)
from collections import defaultdict
def hungarian(graph:dict[int,list[int]], leftNodes:list[int]) -> int:
    # 第一步,构建维护变量。rightMatch维护右集合中各个结点的匹配状态
    rightMatch = {}
    # 第二步,构建dfs递归函数。递归任务:判断左集合中结点u是否能成功匹配。如果u相邻结点v没有匹配,则直接匹配;如果v已经匹配,则将v标记为已访问,dfs(match[v])观察是否能让match[v]结点匹配非v结点来释放v结点,让u进行匹配,即贪心地进行匹配。
    def dfs(u:int) -> bool:
        # 2.1.枚举u的相邻的右集合结点v
        for v in graph[u]:
            # 2.2.判断v是否已访问,已访问则跳过
            if not visited[v]:
                # 2.3.标记v为已访问,并根据条件判断是否能匹配,能匹配的情况下记录右结点v的匹配状态到rightMatch中
                visited[v] = True
                if v not in rightMatch or dfs(rightMatch[v]):
                    rightMatch[v] = u
                    return True
        # 2.4.如果最终都没能匹配一个,则返回False
        return False
    # 第三步,枚举每一个左集合结点u,调用dfs判断各个u结点是否能成功进行匹配,并记录最大匹配数到ans中
    ans = 0
    for u in leftNodes:
        visited = defaultdict(bool)
        if dfs(u):
            ans += 1
    return ans



# ==> Hopcroft-karp算法(时间复杂度:O(sqrt(V)*E))
# params:graph:二分图的邻接表;leftNodes:二分图左集合的所有结点数组;
# return:返回二分图中的最大匹配数(等于最小点覆盖数)
from collections import deque
def hopcroftKarp(graph:dict[int, list[int]], leftNodes:list[int]) -> int:
    inf = float("inf")
    # 第一步,构建维护变量。match_维护二分图中各个匹配关系;dist维护各个交替路中距离左集合中未匹配点的最短距离,dist中映射值为inf代表结点已经匹配或者不存在以该结点开头的增广路径
    match_ = {}
    dist = {}
    # 第二步,构建广搜函数。bfs功能:返回在match_和dist的情况下,是否还存在更多的增广路径,同时更新dist
    def bfs() -> bool:
        # 2.1.将左侧集合的未匹配结点全部加入到队列中,作为BFS的起点;同时将左集合中已经匹配的结点的距离值设置为inf
        que = deque()
        for u in leftNodes:
            if u not in match_:
                que.append(u)
                dist[u] = 0
            else:
                dist[u] = inf
        # 2.2.广搜二分图。使用found维护图中是否还存在增广路径
        found = False
        while que:
            u = que.popleft()
            for v in graph[u]:
                if v not in match_:
                    # 2.2.1.如果右侧结点v没有在match_中,说明v结点还没有匹配,表示图中还存在增广路径,设置found=True
                    found = True
                elif dist[match_[v]] == inf:
                    # 2.2.2.如果右侧结点v在match_中且对应的match_[v]结点还没有被广搜到(即dist[match_[v]==inf]),说明v结点已经匹配,则将v的匹配结点match_[v]加入到广搜队列中,继续寻找增广路径;并更新match_[v]结点所在的最小广搜层次
                    que.append(match_[v])
                    dist[match_[v]] = dist[u] + 1
        # 2.3.返回图中是否还存在增广路径
        return found
    # 第三步,构建受限深搜函数。深搜任务:判断以结点u开头是否存在增广路径;如果存在,则在该路径上进行结点匹配,反转该增广路径。
    def dfs(u:int) -> bool:
        for v in graph[u]:
            if v not in match_ or (dist[match_[v]] == dist[u] + 1 and dfs(match_[v])):
                # 3.1.如果结点v没有匹配,则u->v就是最短的增广路径,直接匹配u和v并返回即可;如果结点v已经匹配了,只有满足match_[v]是广搜中结点u的下一层级时(即dist[match_[v]]==dist[u]+1)(这个条件也能过滤掉递归时match_[v]遍历到相邻结点v的情况),才能继续dfs判断是否存在增广路径,如果存在则匹配并反转该增广路径
                match_[u] = v
                match_[v] = u
                return True
        # 3.2.如果不存在以u开头的增广路径,则在dist中进行标记,并返回False
        dist[u] = inf
        return False
    # 第四步,循环地进行bfs判断图中是否存在增广路径;如果存在则遍历所有左侧没有匹配的结点u,dfs寻找是否存在u开头的最短增广路径,并在dfs中更新match_和dist,如果存在以u开头的增广路径,则通过反转增广路径进行匹配,并将ans自增1
    ans = 0
    while bfs():
        for u in leftNodes:
            if u not in match_ and dfs(u):
                ans += 1
    return ans


class Solution:
    def getGraphAndNodes(self, grid: List[List[int]]):
        m, n = len(grid), len(grid[0])
        # 第一步,构建二分图。在所有值为1的结点中,其中偶数下标行的偶数下标结点和奇数下标行的奇数下标结点为左结点集合uSet,其余结点为右结点集合vSet;uSet和vSet中彼此相邻代表一条边
        graph = defaultdict(list)
        leftNodes, rightNodes = [], []
        for i in range(m):
            for j in range(n):
                if grid[i][j] == 1:
                    # 1.1.将结点添加到图中
                    node = i * n + j
                    if (i % 2 == 0 and j % 2 == 0) or (i % 2 == 1 and j % 2 == 1):
                        leftNodes.append(node)
                    else:
                        rightNodes.append(node)
                    graph[node] = []
                    # 1.2.将边添加到图中
                    for dr, dc in [[-1, 0], [0, -1], [1, 0], [0, 1]]:
                        nr, nc = i + dr, j + dc
                        if 0 <= nr < m and 0 <= nc < n and grid[nr][nc] == 1:
                            node2 = nr * n + nc
                            graph[node].append(node2)
        # print(graph)
        return graph, leftNodes, rightNodes

    # 思路2:二分图+Hopcroft-karp算法
    def minimumOperations(self, grid: List[List[int]]) -> int:
        graph, leftNodes, rightNodes = self.getGraphAndNodes(grid)
        # 计算最大匹配数
        maxMatching = hopcroftKarp(graph, leftNodes)
        return maxMatching

4.执行结果

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

MySQL高可用集群

https://dev.mysql.com/doc/mysql-shell/8.4/en/mysql-innodb-cluster.html 1 什么是MySQL高可用集群 MySQL高可用集群&#xff1a;MySQL InnoDB ClusterInnoDB Cluster是MySQL官方实现高可用读写分离的架构方案&#xff0c;包含以下组件 MySQL Group Replication&#xff1a;简…

day14 leetcode-hot100-27(链表6)

21. 合并两个有序链表 - 力扣&#xff08;LeetCode&#xff09; 1. 暴力法 思路 创建一个空节点&#xff0c;用来组装这两个链表&#xff0c;谁小谁就是下一个节点。 知识 创建空节点&#xff1a;ListNode n1 new ListNode(-1); 具体代码 /*** Definition for singly-l…

YOLOv5 :训练自己的数据集

- **&#x1f368; 本文为[&#x1f517;365天深度学习训练营](https://mp.weixin.qq.com/s/rnFa-IeY93EpjVu0yzzjkw) 中的学习记录博客** - **&#x1f356; 原作者&#xff1a;[K同学啊](https://mtyjkh.blog.csdn.net/)** 我们接着上一篇文章配置完YOLOv5需要的环境后&#…

flutter项目迁移空安全

重中之重 备份好项目文件&#xff0c;甚至连已经加载好的flutter库也可以备份。环境包升级 2.1 不要直接换成flutter:3.0以上的版本&#xff0c;这样做既有基本的库兼容问题&#xff0c;又有空安全下的语法问题(整个项目中需要增加 late、?、!的语法错误&#xff0c;一片报错的…

MySql(十二)

目录 MySql约束 1.添加主键约束 语法格式 1&#xff09;创建一个带主键的表 查看表结构 2&#xff09;创建表的时候指定主键名称 查看表结构 3&#xff09;创建一个表然后&#xff0c;然后再使用alter为列添加主键 查看表结构 4&#xff09;为表添加数据 1---正常数据 2---主键…

51c视觉~3D~合集3

我自己的原文哦~ https://blog.51cto.com/whaosoft/13954440 #SceneTracker 在4D时空中追踪万物&#xff01;国防科大提出首个长时场景流估计方法 本篇分享 TPAMI 2025 论文​​SceneTracker: Long-term Scene Flow Estimation Network​​&#xff0c;国防科大提出首…

【2025年电工杯数学建模竞赛A题】光伏电站发电功率日前预测问题+完整思路+paper+源码

本人7年数学建模竞赛经验&#xff0c;历史获奖率百分之百。团队成员都是拿过全国一等奖的硕博&#xff0c;有需要数模竞赛帮助的可以私信我 本题主要涉及数据预测&#xff0c;数据分析&#xff0c;机器学习&#xff0c;时间序列等知识 1.问题背景与问题描述 2.解题思路分析 …

OpenCv高阶(十九)——dlib关键点定位

文章目录 一、什么是人脸关键点定位&#xff1f;二、关键点模型的下载及关键信息的理解三、dlib关键点定位的简单实现&#xff08;1&#xff09;导入必要的库&#xff08;2&#xff09;从指定路径读取图像文件&#xff08;3&#xff09;创建dlib的正面人脸检测器对象&#xff0…

BUUCTF之[ACTF2020 新生赛]BackupFile

打开环境就一句话 找出源文件! 结合题目名字&#xff1a;BackupFile 先用dirsearct扫描网站文件 发现一个index.php.bak ,拼接url下载 打开发现php代码 <?php include_once "flag.php";if(isset($_GET[key])) {$key $_GET[key];if(!is_numeric($key)) {exit…

头歌之动手学人工智能-Pytorch 之autograd

目录 第1关&#xff1a;Variable 任务描述 编程要求 测试说明 没有伟大的愿望&#xff0c;就没有伟大的天才。——巴尔扎克开始你的任务吧&#xff0c;祝你成功&#xff01; 第2关&#xff1a;Variable 属性 任务描述 编程要求 测试说明 真正的科学家应当是个幻想家&a…

Kafka集成Flume/Spark/Flink(大数据)/SpringBoot

Kafka集成Flume Flume生产者 ③、安装Flume&#xff0c;上传apache-flume的压缩包.tar.gz到Linux系统的software&#xff0c;并解压到/opt/module目录下&#xff0c;并修改其名称为flume Flume消费者 Kafka集成Spark 生产者 object SparkKafkaProducer{def main(args:Array[S…

Scratch节日 | 拯救屈原 | 端午节

端午节快乐&#xff01; 这款特别为端午节打造的Scratch游戏 《拯救屈原》&#xff0c;将带你走进古代中国&#xff0c;感受历史与文化的魅力&#xff01; &#x1f3ee; 游戏介绍 扮演勇敢的探险者&#xff0c;穿越时空回到古代&#xff0c;解锁谜题&#xff0c;完成任务&…

rabbitmq Direct交换机简介

在实际开发中&#xff0c;需求可能变得复杂&#xff0c;如消息的收发和处理。以支付系统为例&#xff0c;成功支付后需要改变订单状态并通知用户&#xff0c;而失败则不需要。为处理这种情况&#xff0c;提出了使用Direct交换机&#xff0c;它可以根据规则将消息路由到指定队列…

Git实战--基于已有分支克隆进行项目开发的完整流程

Git克隆项目开发流程 ✅ 一、完整流程概述✅ 二、详细操作步骤Step 1&#xff1a;克隆仓库&#xff08;如果尚未克隆&#xff09;Step 2&#xff1a;获取远程分支信息并切换到 feature/ 获取所有远程分支Step 3&#xff1a;创建并切换到你的新分支Step 4&#xff1a;开始开发新…

2_MCU开发环境搭建-配置MDK兼容Keil4和C51

MCU开发环境搭建-配置MDK兼容Keil4和C51 一、概述 本文以MDK-ARM V5.36版本基础介绍DMK-ARM工程兼容Keil4和C51的配置。 注:在阅读本文前,请先安装和配置完成MDK-ARM(Keil5)。 二、工具包下载 链接: https://pan.baidu.com/s/1Tu2tDD6zRra4xb_PuA1Wsw 提取码: 81pp 三、…

通过远程桌面连接Windows实例提示“出现身份验证错误,无法连接到本地安全机构”错误怎么办?

本文介绍通过远程桌面连接Windows实例提示“出现身份验证错误无法连接到本地安全机构”错误的解决方案。 问题现象 通过本地电脑内的远程桌面连接Windows实例提示“出现身份验证错误&#xff0c;无法连接到本地安全机构”错误。 问题原因 导致该问题的可能原因如下&#x…

百度golang研发一面面经

输入一个网址&#xff0c;到显示界面&#xff0c;中间的过程是怎样的 IP 报文段的结构是什么 Innodb 的底层结构 知道几种设计模式 工厂模式 简单工厂模式&#xff1a;根据传入类型参数判断创建哪种类型对象工厂方法模式&#xff1a;由子类决定实例化哪个类抽象工厂模式&#…

TC3xx学习笔记-启动过程详解(一)

文章目录 前言Firmware启动过程BMHD Check流程ABM启动Internal Flash启动Bootloader ModeProcessing in case no valid BMHD foundProcessing in case no Boot Mode configured by SSW 总结 前言 之前介绍过UCB BMHD的使用&#xff0c;它在启动过程中起着重要的作用&#xff0…

Scratch节日 | 六一儿童节抓糖果

六一儿童节怎么能没有糖果&#xff1f;这款 六一儿童节抓糖果 小游戏&#xff0c;让你变身小猫&#xff0c;开启一场甜蜜大作战&#xff01; &#x1f3ae; 游戏玩法 帮助小猫收集所有丢失的糖果&#xff0c;收集越多分数越高&#xff01; 小心虫子一样的“坏糖果”&#xff…