LeetCode 周赛上分之旅 # 37 多源 BFS 与连通性问题

⭐️ 本文已收录到 AndroidFamily,技术和职场问题,请关注公众号 [彭旭锐] 和 BaguTree Pro 知识星球提问。

学习数据结构与算法的关键在于掌握问题背后的算法思维框架,你的思考越抽象,它能覆盖的问题域就越广,理解难度也更复杂。在这个专栏里,小彭与你分享每场 LeetCode 周赛的解题报告,一起体会上分之旅。

本文是 LeetCode 上分之旅系列的第 37 篇文章,往期回顾请移步到文章末尾~

周赛 357

T1. 故障键盘(Easy)

  • 标签:模拟、字符串

T2. 判断是否能拆分数组(Medium)

  • 标签:思维

T3. 找出最安全路径(Medium)

  • 标签:BFS、连通性、分层并查集、极大化极小、二分查找

T4. 子序列最大优雅度(Hard)

  • 标签:贪心、排序、堆


T1. 故障键盘(Easy)

https://leetcode.cn/problems/faulty-keyboard/

题解(模拟)

简单模拟题。

  • 在遇到 i 字符时对已填入字符进行反转,时间复杂度是 O(n^2);
  • 使用队列和标记位可以优化时间复杂度,在遇到 i 时修改标记位和写入方向,在最后输出时根据标记位输出,避免中间的反转操作。
class Solution {
public:string finalString(string s) {vector<char> dst;for (auto& c : s) {if (c == 'i') {reverse(dst.begin(), dst.end());} else {dst.push_back(c);}}return string(dst.begin(), dst.end());}
};
class Solution {
public:string finalString(string s) {deque<char> dst;bool rear = true;for (auto& c : s) {if (c == 'i') {rear = !rear;} else {if (rear) {dst.push_back(c);} else {dst.push_front(c);}}}return rear ? string(dst.begin(), dst.end()) : string(dst.rbegin(), dst.rend());}
};

复杂度分析:

  • 时间复杂度: O ( n ) O(n) O(n) 线性遍历和输出时间;
  • 空间复杂度: O ( n ) O(n) O(n) 临时字符串空间。

T2. 判断是否能拆分数组(Medium)

https://leetcode.cn/problems/check-if-it-is-possible-to-split-array/

题解(思维题)

思维题,主要题目的两个条件只要满足其中一个即可 😭

  • 条件 1:子数组的长度为 1 ⇒ 说明数组长度小于等于 2 的时候,一定可以满足(子数组的长度不大于 1);
  • 条件 2:子数组元素之和大于或等于  m ⇒ 需满足子数组 {a1, a2, a3} 与 {a4, a5, a6} 的子数组和均大于等于 m。

结合两个条件,如果我们能找到两个相邻的元素之和大于等于 m,那么总可以通过消除 1 个元素的方式完成题目要求。

例如在示例 3 [2, 3, 3, 2, 3] 中,我们以 [3,3] 为起点倒推:

  • [3, 3]
  • [2, 3, 3] 消除 2
  • [2, 3, 3, 2] 消除 2
  • [2, 3, 3, 2, 3] 消除 3
class Solution {
public:bool canSplitArray(vector<int>& nums, int m) {// 2 | 3, 3 | 2 | 3// 1, 3, 2, 2, 3// 1, 1, 1, 3, 3if (nums.size() <= 2) return true;for (int i = 1; i < nums.size(); i++) {if (nums[i] + nums[i - 1] >= m) return true;}return false;}
};

复杂度分析:

  • 时间复杂度: O ( n ) O(n) O(n) 线性遍历时间;
  • 空间复杂度: O ( 1 ) O(1) O(1) 仅使用常量级别空间。

T3. 找出最安全路径(Medium)

https://leetcode.cn/problems/find-the-safest-path-in-a-grid/

题解一(多源 BFS + 二分答案)

根据题目描述,每个节点的安全系数定位为该节点到「小偷」节点的最小曼哈顿距离,而题目要求是寻找从 [0][0] 到 [n-1][n-1] 的最大安全系数。「使得最小曼哈顿距离最大」暗示可能是需要使用二分答案的极大化极小问题。

  • 多源 BFS 预处理: 先从每个「小偷」节点开始走 BFS 更新相邻节点的最小曼哈顿距离,单次 BFS 的时间复杂度是 O(n^2),虽然我们可以用剪枝优化,但整体的时间复杂度上界是 O(n^4)。为了优化时间复杂度,我们使用多源 BFS(也可以理解为拓扑排序,每次弹出的节点的曼哈顿距离最小),整体的时间仅为 O(n^2);
  • 二分答案: 安全系数与路径可达性存在单调性:
    • 当安全系数越大时,越不容易可达;
    • 当安全系数越小时,越容易可达。
    • 安全系数的下界为 0,上界为 n * 2 - 1,通过二分答案寻找满足可达性的最大安全系数:
class Solution {fun maximumSafenessFactor(grid: List<List<Int>>): Int {val INF = Integer.MAX_VALUEval directions = arrayOf(intArrayOf(0,1), intArrayOf(1,0), intArrayOf(0,-1), intArrayOf(-1,0))val n = grid.size// 特判if (grid[0][0] == 1 || grid[n - 1][n - 1] == 1) return 0// 多源 BFS(拓扑排序)val safe = Array(n) { IntArray(n) { -1 }}var queue = LinkedList<IntArray>()for (r in 0 until n) {for (c in 0 until n) {if (grid[r][c] == 1) {queue.offer(intArrayOf(r, c))safe[r][c] = 0}}}while (!queue.isEmpty()) {val temp = LinkedList<IntArray>()for (node in queue) {for (direction in directions) {val newX = node[0] + direction[0]val newY = node[1] + direction[1]if (newX < 0 || newX >= n || newY < 0 || newY >= n || safe[newX][newY] != -1) continuetemp.offer(intArrayOf(newX, newY))safe[newX][newY] = safe[node[0]][node[1]] + 1}}queue = temp}// for (row in safe) println(row.joinToString())// BFS(检查只通过大于等于 limit 的格子,能否到达终点)fun check(limit: Int) : Boolean {val visit = Array(n) { BooleanArray(n) }var queue = LinkedList<IntArray>()queue.offer(intArrayOf(0, 0))visit[0][0] = truewhile (!queue.isEmpty()) {val temp = LinkedList<IntArray>()for (node in queue) {// 终止条件if (node[0] == n - 1 && node[1] == n - 1) return truefor (direction in directions) {val newX = node[0] + direction[0]val newY = node[1] + direction[1]if (newX < 0 || newX >= n || newY < 0 || newY >= n || visit[newX][newY] || safe[newX][newY] < limit) continuetemp.offer(intArrayOf(newX, newY))visit[newX][newY] = true}}queue = temp}return false}// 二分查找var left = 0var right = Math.min(safe[0][0], safe[n - 1][n - 1])while (left < right) {val mid = (left + right + 1) ushr 1if (!check(mid)) {right = mid - 1} else {left = mid}}return left}
}

复杂度分析:

  • 时间复杂度: O ( n 2 ⋅ l g n 2 ) O(n^2·lgn^2) O(n2lgn2) 其中 多源 BFS 时间为 O ( n 2 ) O(n^2) O(n2),单次检查的 BFS 时间复杂度为 O ( n 2 ) O(n^2) O(n2),二分的次数为 l g n 2 lgn^2 lgn2,整体时间复杂度是 O ( n 2 ⋅ l g n 2 ) O(n^2·lgn^2) O(n2lgn2)
  • 空间复杂度: O ( n 2 ) O(n^2) O(n2) safe 安全系数矩阵空间。

题解二(多源 BFS + 堆)

思路参考雪景式的题解。

在题解一预处理的基础上,同样走一次 BFS 也能够算出最大安全系数,思路类似于 Dijkstra 最最短路算法中使用当前最短路最短的节点去松弛相邻边,我们优先让当前曼哈顿距离最大的节点去松弛相邻节点,以保证每个节点都能够从较大的路径转移过来。

class Solution {fun maximumSafenessFactor(grid: List<List<Int>>): Int {...// 类最短路(使用曼哈顿距离最大的节点去松弛相邻边)val heap = PriorityQueue<IntArray>() { e1, e2 ->e2[0] - e1[0]}heap.offer(intArrayOf(safe[0][0], 0, 0))val visit = Array(n) { BooleanArray(n) }visit[0][0] = truewhile (!heap.isEmpty()) {val node = heap.poll()if (node[1] == n - 1 && node[2] == n - 1) return node[0]for (direction in directions) {val newX = node[1] + direction[0]val newY = node[2] + direction[1]if (newX < 0 || newX >= n || newY < 0 || newY >= n || visit[newX][newY]) continue// 松弛相邻边heap.offer(intArrayOf(Math.min(node[0], safe[newX][newY]), newX, newY))visit[newX][newY] = true}}return 0}
}

复杂度分析:

  • 时间复杂度: O ( n 2 ⋅ l g n 2 ) O(n^2·lgn^2) O(n2lgn2) 其中 多源 BFS 时间为 O ( n 2 ) O(n^2) O(n2),基于堆的 BFS 的时间复杂度为 O ( n 2 ⋅ l g n 2 ) O(n^2·lgn^2) O(n2lgn2)
  • 空间复杂度: O ( n 2 ) O(n^2) O(n2) safe 安全系数矩阵空间。

题解三(多源 BFS + 分层并查集)

思路参考灵神的题解。

其实,求从 [0][0] 到 [n - 1][n - 1] 的最大安全系数,也相当于连通性问题的变形,而连通性问题有并查集的解法。为了求得最大安全系数,我们使用分层并查集:

  • 首先,在预处理阶段求出每个节点的最小曼哈顿距离,并将节点按照曼哈顿距离分类;
  • 其次,我们从最大的曼哈顿距离开始逆序合并,当 [0][0] 和 [n - 1][n - 1] 连通时返回结果。
class Solution {fun maximumSafenessFactor(grid: List<List<Int>>): Int {val directions = arrayOf(intArrayOf(0,1), intArrayOf(1,0), intArrayOf(0,-1), intArrayOf(-1,0))val n = grid.size// 特判if (grid[0][0] == 1 || grid[n - 1][n - 1] == 1) return 0// 多源 BFS(拓扑排序)val safe = Array(n) { IntArray(n) { -1 }}// 分层val groups = LinkedList<LinkedList<IntArray>>()var queue = LinkedList<IntArray>()for (r in 0 until n) {for (c in 0 until n) {if (grid[r][c] == 1) {queue.offer(intArrayOf(r, c))safe[r][c] = 0}}}groups.add(queue)while (!queue.isEmpty()) {val temp = LinkedList<IntArray>()for (node in queue) {for (direction in directions) {val newX = node[0] + direction[0]val newY = node[1] + direction[1]if (newX < 0 || newX >= n || newY < 0 || newY >= n || safe[newX][newY] != -1) continuetemp.offer(intArrayOf(newX, newY))safe[newX][newY] = safe[node[0]][node[1]] + 1}}queue = tempif (!queue.isEmpty()) groups.add(queue)}// for (row in safe) println(row.joinToString())// for (row in groups) println(row.joinToString())val helper = UnionFind(n)// 逆序合并for (i in groups.size - 1 downTo 0) {for (node in groups[i]) {val x = node[0]val y = node[1]for (direction in directions) {val newX = x + direction[0]val newY = y  + direction[1]// 合并曼哈顿距离大于等于当前层的节点if (newX < 0 || newX >= n || newY < 0 || newY >= n || safe[newX][newY] < i) continuehelper.union(x * n + y, newX * n + newY)}}if (helper.find(0) == helper.find(n * n - 1)) return i}return 0}class UnionFind(private val n: Int) {private val parents = IntArray(n * n) { it }private val ranks = IntArray(n * n)fun find(x: Int): Int {var cur = xwhile (cur != parents[cur]) {parents[cur] = parents[parents[cur]]cur = parents[cur]}return cur}fun union(x: Int, y: Int) {val rootX = find(x)val rootY = find(y)if (ranks[rootX] < ranks[rootY]) {parents[rootX] = rootY} else if (ranks[rootX] > ranks[rootY]){parents[rootY] = rootX} else {parents[rootY] = rootXranks[rootX]++}}}
}

复杂度分析:

  • 时间复杂度: O ( n 2 ) O(n^2) O(n2) 其中 多源 BFS 时间为 O ( n 2 ) O(n^2) O(n2),基于路径压缩和按秩合并的并查集时间复杂度为 O ( n 2 ) O(n^2) O(n2)
  • 空间复杂度: O ( n 2 ) O(n^2) O(n2) safe 安全系数矩阵空间。

T4. 子序列最大优雅度(Hard)

https://leetcode.cn/problems/maximum-elegance-of-a-k-length-subsequence/

题解(反悔贪心 + 堆)

  • 固定一个维度: 题目定义的优雅度 total_profit + distinct_categories^2 存在两个维度的变量,我们考虑固定其中一个维度来简化问题讨论:
    • 对所有节点按照利润从大到小逆序排列,并选择前 k 个节点,此时的 total_profit 是最大的;
    • 在此基础上,我们继续遍历剩余的 n - k 个节点,并考虑替换前 k 个节点中的某个节点,由于已经选择的节点 total_profit 是最大的,因此需要让替换后的类目数变多。
  • 分类讨论(替换哪个):
    • 1、如果某个已选节点与第 i 个节点的类目相同,那么替换后不会让类目数变大,不可能让优雅度变大;
    • 2、如果某个已选节点与第 i 个节点的类目不同,但只出现一次,那么替换出不会让类目变大,不可能让优雅度变大。否则,如果出现多次,替换后类目数变大,有可能让优雅度变大;
    • 3、为了让优雅度尽可能大,我们期望替换后的 total_profit 的减少量尽可能小,同时数目类别应该增大,否则无法获得更大的优雅度。为了让替换后的 total_profit 的减少量尽可能小,我们应该替换已选列表中利润最小同时重复的节点。
  • 怎么高效替换:
    • 使用堆维护利润最小同时重复的元素,由于我们是从大到小线性枚举的,因此直接使用线性表模拟堆的能力;
    • 新替换进去的不会被替换出去(想想为什么)。
class Solution {fun findMaximumElegance(items: Array<IntArray>, k: Int): Long {Arrays.sort(items) { e1, e2 ->e2[0] - e1[0]}var ret = 0Lvar totalProfit = 0L// duplicate:小顶堆val duplicate = LinkedList<Int>()// categorySet:类目表val categorySet = HashSet<Int>()for ((i, item) in items.withIndex()) {val profit = item[0]val category = item[1]if (i < k) {totalProfit += item[0]// 记录重复节点if (categorySet.contains(category)) {duplicate.add(profit)}categorySet.add(category)} else if (!duplicate.isEmpty() && !categorySet.contains(category)){// 替换totalProfit += profit - duplicate.pollLast()categorySet.add(category)} else {// 不会让类目数量变大}// println(duplicate.joinToString())ret = Math.max(ret, totalProfit + 1L * categorySet.size * categorySet.size)}return ret}
}

复杂度分析:

  • 时间复杂度: O ( n l g n ) O(nlgn) O(nlgn) 瓶颈在排序;
  • 空间复杂度: O ( n ) O(n) O(n) 堆空间。

推荐阅读

LeetCode 上分之旅系列往期回顾:

  • LeetCode 单周赛第 356 场 · KMP 字符串匹配殊途同归
  • LeetCode 单周赛第 355 场 · 两题坐牢,菜鸡现出原形
  • LeetCode 双周赛第 109 场 · 按部就班地解决动态规划问题
  • LeetCode 双周赛第 107 场 · 很有意思的 T2 题

⭐️ 永远相信美好的事情即将发生,欢迎加入小彭的 Android 交流社群~

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

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

相关文章

python高阶技巧

目录 设计模式 单例模式 具体用法 工厂模式 优点 闭包 案例 修改闭包外部变量 闭包优缺点 装饰器 装饰器原理 装饰器写法 递归 递归的调用过程 递归的优缺点 用递归计算阶乘 设计模式 含义&#xff1a;设计模式是一种编程套路&#xff0c;通过这种编程套路可…

AQL品质抽样标准

AQL抽样标准 - 百度文库 Acceptance Quality Limit 接收质量限的缩写&#xff0c;即当一个连续系列批被提交验收时&#xff0c;可允许的最差过程平均质量水平。 AQL普遍应用于各行业产品的质量检验&#xff0c;不同的AQL标准应用于不同物质的检验上。在AQL 抽样时&#xff0c;…

【开源三方库】Aki:一行代码极简体验JSC++跨语言交互

开源项目 OpenHarmony 是每个人的 OpenHarmony 一、简介 OpenAtom OpenHarmony&#xff08;以下简称“OpenHarmony”&#xff09;的前端开发语言是ArkTS&#xff0c;在TypeScript&#xff08;简称TS&#xff09;生态基础上做了进一步扩展&#xff0c;继承了TS的所有特性&#x…

SpringBoot开发环境热部署

目录 开发热部署 添加dev-tools依赖 在application.properties中配置devtools 在IDEA中添加设置 开发热部署 在实际的项目开发调试过程中会频繁地修改后台类文件&#xff0c;导致需要重新编译、 重新启动&#xff0c;整个过程非常麻烦&#xff0c;影响开发…

STM32 低功耗-待机模式

STM32 待机模式 文章目录 STM32 待机模式第1章 低功耗模式简介第2章 待机模式简介2.1 进入待机模式2.1 退出待机模式 第3章 待机模式代码部分总结 第1章 低功耗模式简介 在 STM32 的正常工作中&#xff0c;具有四种工作模式&#xff1a;运行、睡眠、停止和待机模式。 在系统或…

【Spring Cloud 六】Hystrix熔断

这里写目录标题 系列文章目录背景一、Hystrix是什么服务雪崩服务容错的相关概念熔断器降级超时控制限流 二、会什么要有Hystrix三、如何使用Hystrix进行熔断处理整体项目代码服务提供者pom文件yml配置文件启动类controller 服务消费者pom文件yml配置文件启动类feignhystrixcont…

信息安全技术工业控制系统安全控制应用指南学习笔记

工业控制系统安全控制基线 根据工业控制系统在国家安全、经济建设、社会生活中的重要程度&#xff0c;遭到破坏后对国家安全、社会秩序、公共利益以及公民、法人和其他组织的合法权益的危害程度等&#xff0c;结合信息安全等级保护标准划分及实施效果分析&#xff0c;结合工业…

前端主题切换方案——CSS变量

前言 主题切换是前端开发中老生常谈的问题&#xff0c;本文将介绍主流的前端主题切换实现方案——CSS变量 CSS变量 简介 编写CSS样式时&#xff0c;为了避免代码冗余&#xff0c;降低维护成本&#xff0c;一些CSS预编译工具&#xff08;Sass/Less/Stylus&#xff09;等都支…

JVM面试题

JVM理论 #JVM内存模型# Java内存模型&#xff08;JMM&#xff09;&#xff1f; Java的内存模型决定了线程间的通信方式&#xff0c;JMM的模型是由主存和工作内存构成&#xff0c;两个线程想要正常通信需要将工作内存中的变量刷到主存中&#xff0c;另一个线程才能正确读取得…

【数据结构】移除链表元素-图文解析(单链表OJ题)

LeetCode链接&#xff1a;203. 移除链表元素 - 力扣&#xff08;LeetCode&#xff09; 本文导航 &#x1f4ad;做题思路 &#x1f3a8;画图更好理解&#xff1a; ✍️代码实现 &#x1f5c2;️分情况讨论&#xff1a; ❄️极端情况&#xff1a; &#x1f4ad;做题思路 遍历链表…

Git报错合集

本文记录了笔者在使用 github 过程中遇到的问题&#xff0c;仅供个人使用。 目录 Could not resolve hostlocal changes to the following files would be overwritten by mergeTLS connection was non-properly terminatedUpdates were rejected because the remote contains …

普及100Hz高刷+1ms响应 微星发布27寸显示器:仅售799元

不论办公还是游戏&#xff0c;高刷及低响应时间都很重要&#xff0c;微星现在推出了一款27寸显示器PRO MP273A&#xff0c; 售价只有799元&#xff0c;但支持100Hz高刷、1ms响应时间&#xff0c;还有FreeSync技术减少撕裂。 PRO MP273A的100Hz高刷新率是其最大的卖点之一&#…

scala连接mysql数据库

scala中通常是通过JDBC组件来连接Mysql。JDBC, 全称为Java DataBase Connectivity standard。 加载依赖 其中包含 JDBC driver <dependency><groupId>mysql</groupId><artifactId>mysql-connector-java</artifactId><version>8.0.29&l…

eclipse Java Code_Style Code_Templates

Preferences - Java - Code Style - Code Templates Eclipse [Java_Code_Style_Code_Templates_ZengWenFeng] 2023.08.07.xml 创建一个新的工程&#xff0c;不然有时候不生效&#xff0c;旧项目可能要重新导入eclipse 创建一个测试类试一试 所有的设置都生效了

Java8实战-总结11

Java8实战-总结11 Lambda表达式方法引用管中窥豹如何构建方法引用 构造函数引用 Lambda表达式 方法引用 方法引用让你可以重复使用现有的方法定义&#xff0c;并像Lambda一样传递它们。在一些情况下&#xff0c;比起使用Lambda表达式&#xff0c;它们似乎更易读&#xff0c;感…

算法竞赛入门【码蹄集新手村600题】(MT1120-1140)C语言

算法竞赛入门【码蹄集新手村600题】(MT1120-1140&#xff09;C语言 目录MT1121 小码哥考完咯MT1122阶梯IF-ELSEMT1123 元音MT1124 罗马数字MT1125 几月份MT1126 十二生肖MT1127 小码哥的属相MT1128 骰子的反面MT1129 小码哥玩骰子MT1130 骰子里面的数学MT1131 字符判断MT1132 人…

SciencePub学术 | 算法类重点SCIEEI征稿中

SciencePub学术 (www.sciencepub.cn) 刊源推荐: 算法类重点SCIE&EI征稿中&#xff01;2区闭源好刊&#xff0c;对国人非常友好。信息如下&#xff0c;录满为止&#xff1a; 一、期刊概况&#xff1a; 算法类重点SCIE&EI 【期刊简介】IF&#xff1a;6.0-6.5&#xff0…

Acwing.876 快速幂求逆元

题目 给定n组ai ,pi&#xff0c;其中p;是质数,求α;模p;的乘法逆元&#xff0c;若逆元不存在则输出impossible。 输入格式 第一行包含整数n。 接下来n行&#xff0c;每行包含一个数组ai, pi&#xff0c;数据保证p;是质数。 输出格式 输出共n行&#xff0c;每组数据输出一…

钉钉对接打通金蝶云星空获取流程实例列表详情(宜搭)接口与其他应收单接口

钉钉对接打通金蝶云星空获取流程实例列表详情&#xff08;宜搭&#xff09;接口与其他应收单接口 对接系统钉钉 钉钉&#xff08;DingTalk&#xff09;是阿里巴巴集团专为中国企业打造的免费沟通和协同的多端平台&#xff0c;提供PC版&#xff0c;Web版和手机版&#xff0c;有考…

pytorch学习——卷积神经网络——以LeNet为例

目录 一.什么是卷积&#xff1f; 二.卷积神经网络的组成 三.卷积网络基本元素介绍 3.1卷积 3.2填充和步幅 3.2.1填充&#xff08;Padding&#xff09; 填充是指在输入数据周围添加额外的边界值&#xff08;通常是零&#xff09;&#xff0c;以扩展输入的尺寸。填充可以在卷…