LeetCode 1631. Path With Minimum Effort【最小瓶颈路;二分+BFS或DFS;计数排序+并查集;最小生成树】1947

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

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

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

你准备参加一场远足活动。给你一个二维 rows x columns 的地图 heights ,其中 heights[row][col] 表示格子 (row, col) 的高度。一开始你在最左上角的格子 (0, 0) ,且你希望去最右下角的格子 (rows-1, columns-1) (注意下标从 0 开始编号)。你每次可以往  四个方向之一移动,你想要找到耗费 体力 最小的一条路径。

一条路径耗费的 体力值 是路径上相邻格子之间 高度差绝对值 的 最大值 决定的。

请你返回从左上角走到右下角的最小 体力消耗值 。

示例 1:

输入:heights = [[1,2,2],[3,8,2],[5,3,5]]
输出:2
解释:路径 [1,3,5,3,5] 连续格子的差值绝对值最大为 2 。
这条路径比路径 [1,2,2,2,5] 更优,因为另一条路径差值最大值为 3

示例 2:

输入:heights = [[1,2,3],[3,8,4],[5,3,5]]
输出:1
解释:路径 [1,2,3,4,5] 的相邻格子差值绝对值最大为 1 ,比路径 [1,3,5,3,5] 更优。

示例 3:

输入:heights = [[1,2,1,1,1],[1,2,1,2,1],[1,2,1,2,1],[1,2,1,2,1],[1,1,1,2,1]]
输出:0
解释:上图所示路径不需要消耗任何体力。

提示:

  • rows == heights.length
  • columns == heights[i].length
  • 1 <= rows, columns <= 100
  • 1 <= heights[i][j] <= 10^6

同类题目:

  • LeetCode 2812. Find the Safest Path in a Grid
  • LeetCode 778. Swim in Rising Water
  • LeetCode 1631. 最小体力消耗路径

这几道题,虽然物理问题不同,但是背后的数学模型几乎完全相同,唯一的区别在于:

  • 第 1631 题将相邻格子的高度差作为边的权重,是找最小化的 相邻格子最大绝对高度差。
  • 第 778 题将相邻两格子间高度的最大值作为边的权重,是找最小化的 最大高度
  • 第 2812 题将当前方格到最近小偷的距离作为边的权重,是找最大化的 最小距离

无向图 G G G x x x y y y最小瓶颈路是这样的一类简单路径,满足这条路径上的最大边权在所有 x x x y y y 的简单路径中是最小的。
根据最小生成树定义, x x x y y y 的最小瓶颈路上的最大边权等于最小生成树上 x x x y y y 路径上的最大边权。虽然最小生成树不唯一,但是每种最小生成树 x x x y y y 路径的最大边权相同且为最小值。也就是说,每种最小生成树上的 x x x y y y 的路径均为最小瓶颈路。

无向图 G G G x x x y y y最大瓶颈路是这样的一类简单路径,满足这条路径上的最小边权在所有 x x x y y y 的简单路径中是最大的。
根据最小生成树定义, x x x y y y 的最大瓶颈路上的最小边权等于最大生成树上 x x x y y y 路径上的最小边权。虽然最大生成树不唯一,但是每种最大生成树 x x x y y y 路径的最小边权相同且为最大值。也就是说,每种最大生成树上的 x x x y y y 的路径均为最大瓶颈路。

我们不必实际求出整个最小/大生成树,只需要求出最小/大生成树上 x x x y y y 路径上的最大/小边权

在看清楚物理问题背后的数学模型后,这几道题都会迎刃而解。

解法1 二分+BFS/DFS

这种做法是LeetCode 2812. Find the Safest Path in a Grid的完全简化版、在2812中需要先进行一次多源BFS、再来二分答案,也用在LeetCode 1631. 最小体力消耗路径中。

根据题目中的描述,给定了 h e i g h t s [ i ] [ j ] heights[i][j] heights[i][j] 的范围是 [ 1 , 1 0 6 ] [1, 10^6 ] [1,106] ,所以答案(绝对高度差)必然落在范围 [ 0 , 1 0 6 ] [0, 10^6] [0,106]

  • 如果允许消耗的体力值 h h h 越低,网格上可以越过的部分就越少,存在从左上角到右下角的一条路径的可能性越小
  • 如果允许消耗的体力值 h h h 越高,网格上可以游泳的部分就越多,存在从左上角到右下角的一条路径的可能性越大。

这是本问题具有的 单调性 。因此可以使用二分查找定位到最小体力消耗值。

假设最优解为 l l l 的话(恰好能到达右下角的体力消耗),那么小于 l l l 的体力消耗无法到达右下角,大于 l l l 的体力消耗能到达右下角,因此在以最优解 l l l 为分割点的数轴上具有二段性,可以通过「二分」找到分割点 l l l

注意:「二分」的本质是两段性,并非单调性。只要一段满足某个性质,另外一段不满足某个性质,就可以用「二分」。其中 33. 搜索旋转排序数组 是一个很好的说明例子。

具体来说:在区间 [ 0 , 1 e 6 ] [0,1e6] [0,1e6] 里猜一个整数,针对这个整数从起点(左上角)开始做一次DFS或BFS:

  • 当小于等于该数值时,如果存在一条从左上角到右下角的路径,说明答案可能是这个数值,也可能更小
  • 当小于等于该数值时,如果不存在一条从左上角到右下角的路径,说明答案一定比这个数值更大。
  • 按照这种方式不断缩小搜索的区间,最终找到最小体力消耗。
class Solution {
public:int minimumEffortPath(vector<vector<int>>& heights) {int n = heights.size(), m = heights[0].size();typedef pair<int, int> pii;int d[4][2] = {-1, 0, 1, 0, 0, -1, 0, 1};auto check = [&](int lim) {bool vis[n][m]; memset(vis, false, sizeof(vis));queue<pii> q;q.push(pii(0, 0)); vis[0][0] = true;while (!q.empty()) {pii p = q.front(); q.pop();int i = p.first, j = p.second;if (i == n - 1 && j == m - 1) return true;for (int k = 0; k < 4; ++k) {int x = i + d[k][0], y = j + d[k][1];if (x < 0 || x >= n || y < 0 || y >= m || abs(heights[x][y] - heights[i][j]) > lim || vis[x][y])continue;q.push(pii(x, y));vis[x][y] = true;}}return vis[n - 1][m - 1];};int l = 1, r = 1e6 + 1;while (l < r) {int mid = (l + r) >> 1;if (check(mid)) r = mid;else l = mid + 1;}return l;}
};

复杂度分析:

  • 时间复杂度: O ( M N log ⁡ C ) O(MN \log C) O(MNlogC) 。 其中 N , M N,M N,M 是方格的边长。最差情况下进行 log ⁡ C ( C = 1 e 6 ) \log C\ (C = 1e6) logC (C=1e6) 次二分查找,每一次二分查找最差情况下要遍历所有单元格一次,时间复杂度为 O ( M N ) O(MN) O(MN)
  • 空间复杂度: O ( M N ) O(MN) O(MN) 。 数组 v i s vis vis 的大小为 M N MN MN ,如果使用深度优先遍历,须要使用的栈的大小最多为 M N MN MN ,如果使用广度优先遍历,须要使用的队列的大小最多为 M N MN MN

解法2 最小瓶颈路模型+最小生成树(Prim+堆/Kruskal+边集数组)

根据题意,我们要找的是从左上角到右下角的最优路径,其中最优路径是指途径的边的最大权重值最小,然后输出最优路径中的最大权重值。此处的边权为途径节点间高度差绝对值

根据最小瓶颈路模型,我们可以使用Kruskal最小生成树算法:

  1. 先遍历所有的点,将所有的边加入集合,存储的格式为数组 [ a , b , w ] [a, b, w] [a,b,w] ,代表编号为 a a a 的点和编号为 b b b 的点之间的权重为 w w w(按照题意, w w w 为两者的高度绝对差)。
  2. 对边集集合进行排序,按照 w w w 进行从小到达排序
  3. 当我们有了所有排好序的候选边集合之后,对边从前往后处理,选择那些不会出现环的边加入最小生成树中。
  4. 每次加入一条边之后,使用并查集来查询左上角点和右下角点是否连通。如果连通,那么该边的权重即是答案
class Solution {private class UnionFind {private int[] parent;public UnionFind(int n) {parent = new int[n];for (int i = 0; i < n; ++i) parent[i] = i;}public int find(int x) {return x != parent[x] ? parent[x] = find(parent[x]) : x;}public boolean isConnected(int x, int y) { return find(x) == find(y); }public void union(int p, int q) {int rp = find(p), rq = find(q);if (rp == rq) return;parent[rp] = rq;}}public int minimumEffortPath(int[][] grid) {int n = grid.length, m = grid[0].length;int len = n * m;int[][] d = {{0, 1}, {0, -1}, {1, 0}, {-1, 0}};UnionFind unionFind = new UnionFind(len);// 预处理所有无向边// edge存[a,b,w]: 代表从a到b所需的时间点为w// 虽然我们可以往四个方向移动,但只要对于每个点都添加「向右」和「向下」// 两条边的话,其实就已经覆盖了所有边List<int[]> edges = new ArrayList<>();for (int i = 0; i < n; ++i) {for (int j = 0; j < m; ++j) {int index = i * m + j;int a, b, w;if (i + 1 < n) {a = index; b = (i + 1) * m + j;w = Math.abs(grid[i][j] - grid[i + 1][j]);edges.add(new int[]{a, b, w});}if (j + 1 < m) {a = index; b = i * m + (j + 1);w = Math.abs(grid[i][j] - grid[i][j + 1]);edges.add(new int[]{a, b, w});}   }}Collections.sort(edges, (a, b) -> a[2] - b[2]); // 根据w权值升序// 从「小边」开始添加,当某一条边应用之后,恰好使得「起点」和「结点」联通// 那么代表找到了「最短路径」中的「权重最大的边」int start = 0, end = len - 1;for (int[] edge : edges) {int a = edge[0], b = edge[1], w = edge[2];unionFind.union(a, b);if (unionFind.isConnected(start, end)) return w;}return 0;}
}

Kruskal虽然没有求出完整最小生成树,但是对所有边进行了排序。我们可以使用Prim算法+优先队列。

下面将此问题抽象为一张带权有向图,每个单元格为顶点,有指向相邻顶点的有向边,边的权值为指向顶点的高度绝对差。然后用Prim算法,求出最小生成树上左上角的点到右下角的点的路径上的最大边权值。此时不用对所有边进行排序。

class Solution {public int minimumEffortPath(int[][] grid) {int n = grid.length, m = grid[0].length;boolean[][] vis = new boolean[n][m];// 必须将 先前加入队列时的边权重 加入int[]中Queue<int[]> minHeap = new PriorityQueue<>((a, b) -> a[2] - b[2]);minHeap.offer(new int[]{0, 0, 0});int[][] dist = new int[n][m];for (int[] row : dist)Arrays.fill(row, Integer.MAX_VALUE);dist[0][0] = 0;int[][] directions = {{0, 1}, {0, -1}, {1, 0}, {-1, 0}};while (!minHeap.isEmpty()) { // 找最短的边int[] front = minHeap.poll();int x = front[0], y = front[1], d = front[2];if (vis[x][y]) continue;vis[x][y] = true;if (x == n - 1 && y == m - 1) break;// 更新for (int i = 0; i < 4; ++i) {int u = x + directions[i][0], v = y + directions[i][1];if (u >= 0 && u < n && v >= 0 && v < m && !vis[u][v] && // prim算法Math.max(d, Math.abs(grid[x][y] - grid[u][v]))<= dist[u][v]) {dist[u][v] = Math.max(d,Math.abs(grid[x][y] - grid[u][v]));minHeap.offer(new int[]{u, v, dist[u][v]});}}}return dist[n - 1][m - 1];}
}

复杂度分析:

  • 时间复杂度: O ( M N log ⁡ M N ) O(MN \log MN) O(MNlogMN) 。 使用了优先队列的 Prim 算法的时间复杂度是 O ( E log ⁡ E ) O(E \log E) O(ElogE) ,这里 E E E 是边数,至多是顶点数的 4 4 4 倍,顶点数为 M N MN MN ,因此 O ( E log ⁡ E ) = O ( 4 M N log ⁡ M N ) = O ( M N log ⁡ M N ) O(E \log E) = O(4MN \log MN) = O(MN \log MN) O(ElogE)=O(4MNlogMN)=O(MNlogMN)
  • 空间复杂度: O ( M N ) O(MN) O(MN) 。 数组 v i s vis vis d i s t dist dist 的大小为 O ( M N ) O(MN) O(MN) ,优先队列中保存的边的总数也是 M N MN MN 级别的。

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

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

相关文章

流量日志分析--实操

[鹤城杯 2021]流量分析 <--第一道流量分析不难,主要就是布尔盲注的流量包分析,直接查看http请求包即可我们可以通过观察看到注入成功的响应长度不同,这里成功的为978字节,失败的994字节.不要问为什么.其实也可以直接判断.978的流量比994的少了非常多 显然就是成功的(因为这里…

LeetCode--HOT100题(26)

目录 题目描述&#xff1a;142. 环形链表 II&#xff08;中等&#xff09;题目接口解题思路代码 PS: 题目描述&#xff1a;142. 环形链表 II&#xff08;中等&#xff09; 给定一个链表的头节点 head &#xff0c;返回链表开始入环的第一个节点。 如果链表无环&#xff0c;则返…

antd中Switch组件的使用

<Switch> 是 Ant Design 中的一个组件&#xff0c;用于在开关之间切换。checkedChildren 是 <Switch> 组件的一个属性&#xff0c;用于指定在开关打开时显示的文本或 React 元素。 以下是 <Switch> 组件的基本语法&#xff1a; import { Switch } from ant…

【大数据】一些基本概念

一、数据库、数据仓库、数据湖 1.什么是数据库 (Database, DB) 数据库是指长期储存在计算机中的有组织的, 可共享的数据集合 就是存储数据的仓库 数据库有三个特点: 永久存储, 有组织, 可共享 数据库是一种结构化数据存储技术&#xff0c;用于存储和管理有组织的数据。数据库…

在 Linux 中使用 cp 命令

cp 命令是 Linux 中一个重要的命令&#xff0c;你可能经常会用到它。 正如名称所示&#xff0c;cp 代表 复制copy&#xff0c;它被用于 在 Linux 命令行中复制文件和目录。 这是一个相对简单的命令&#xff0c;只有几个选项&#xff0c;但你仍有必要深入了解它。 在展示 cp …

使用GUI Guider工具开发嵌入式GUI应用 (3) - 使用label组件

使用GUI Guider工具开发嵌入式GUI应用 (3) - 使用label组件 文章目录 使用GUI Guider工具开发嵌入式GUI应用 (3) - 使用label组件引言在GUI Guider工程中创建label组件编译MCU工程并下载到开发板 引言 本节讲述在GUI Guider中&#xff0c;应用各种UI的基本元素&#xff0c;并顺…

(十)人工智能应用--深度学习原理与实战--模型的保存与加载使用

目的:将训练好的模型保存为文件,下次使用时直接加载即可,不必重复建模训练。 神经网络模型训练好之后,可以保存为文件以持久存储,这样下次使用时就不重新建模训练,直接加载就可以。TensorfLow提供了灵活的模型保存方案,既可以同时保存网络结构和权重(即保存全模型),也可…

datawhale49期-task02:安装MMSegmentation

task02:安装MMSegmentation 运行环境&#xff1a;window11 ,GPU RTX 4060、CUDA v11.8 1. Pytorch环境 步骤 1. 创建一个 conda 环境&#xff0c;并激活 conda create --name openmmlab python3.8 -y conda activate openmmlabStep 2. 参考 official instructions 安装 PyTor…

详谈数据库InnoDB引擎与MyISAM引擎

目录 1. 简单了解什么是存储引擎? 2. InnoDB 引擎概述 3. MyISAM 引擎概述 4. InnoDB 与 MyISAM 的一些区别 1. 简单了解什么是存储引擎? 相信很多人在听到存储引擎这个名字的时候可能会有些疑惑&#xff0c;听着名字就觉得有些难&#xff0c;导致很多人没有兴趣了解它&a…

【算法题】螺旋矩阵IV (求解n阶折线蛇形矩阵)

一、问题的提出 n阶折线蛇形矩阵的特点是按照图1所示的方式排列元素。n阶蛇形矩阵是指矩阵的大小为nn&#xff0c;其中n为正整数。 题目背景 一个 n 行 n 列的螺旋矩阵可由如图1所示的方法生成&#xff0c;观察图片&#xff0c;找出填数规律。填数规则为从 1 开始填到 nn。 …

HTTP 协议的基本格式和 fiddler 的用法

目录 一. HTTP 协议 1. HTTP协议是什么 2. HTTP协议的基本格式 HTTP请求 首行 GET和POST方法&#xff1a; 其他方法 经典面试题&#xff1a; URL Header(请求报头)部分 空行 ​HTTP响应 状态码总结: 二、Fiddler的用法 1.Fidder的安装 2.Fidder的使用 一. HTTP 协议 1. H…

如何在 iOS 上安装并使用 ONLYOFFICE 文档

借助 iOS 版文档应用&#xff0c;您可在移动端设备上访问存储于 ONLYOFFICE 账户中的文件&#xff0c;查看和编辑现有文本文档、电子表格和演示文稿&#xff0c;创建新文档并对其进行整理&#xff0c;以及连接第三方云存储服务。您可与其他门户网站用户协作编辑文档&#xff0c…

多环境_部署项目

多环境&#xff1a; 指同一套项目代码在不同的阶段需要根据实际情况来调整配置并且部署到不同的机器上。 为什么需要&#xff1f; 1. 每个环境互不影响 2. 区分不同的阶段&#xff1a;开发 / 测试 / 生产 3. 对项目进行优化&#xff1a; 1. 本地日志级别 2. 精简依赖&a…

图像的镜像变换之c++实现(qt + 不调包)

1.基本原理 1.水平镜像变化 设图像的宽度为width&#xff0c;则水平镜像变化的映射关系如下&#xff1a; 2.垂直镜像变化 设图像的宽度为height&#xff0c;则垂直镜像变化的映射关系如下&#xff1a; 2.代码实现&#xff08;代码是我以前自学图像处理时写的&#xff0c;代码很…

ARM(汇编指令)

.global _start _start:/*mov r0,#0x5mov r1,#0x6 bl LoopLoop:cmp r0,r1beq stopsubhi r0,r0,r1subcc r1,r1,r0mov pc,lr*/ mov r0,#0x1mov r1,#0x0mov r2,#0x64bl Loop Loop:cmp r0,r2bhi stopadd r1,r1,r0add r0,r0,#0x01mov pc,lr stop:B stop.end

【Leetcode】基础题||合并有序表(击败100%)

step by step. 题目&#xff1a;&#xff08;超级基础的题&#xff09; 将两个升序链表合并为一个新的 升序 链表并返回。新链表是通过拼接给定的两个链表的所有节点组成的。 示例 1&#xff1a; 输入&#xff1a;l1 [1,2,4], l2 [1,3,4] 输出&#xff1a;[1,1,2,3,4,4]示例…

自动测试框架airtest应用一:将XX读书书籍保存为PDF

一、Airtest的简介 Airtest是网易出品的一款基于图像识别和poco控件识别的一款UI自动化测试工具。Airtest的框架是网易团队自己开发的一个图像识别框架&#xff0c;这个框架的祖宗就是一种新颖的图形脚本语言Sikuli。Sikuli这个框架的原理是这样的&#xff0c;计算机用户不需要…

React Dva项目小优化之redux-action

之前 我们讲过 models 接下啦 我们来给大家讲一个新的库 这个库的话 有最好 没有影响也不大 它主要是帮助我们处理 action的 我们直接在 GitHub 官网上搜索 redux-action 我们搜出来 第一个就是 从星数来看 还是非常优秀的 我们拉下来 找到这个Documentation 然后点击进去 进…

Jmeter - 函数助手

目录 __StringFromFile __CSVRead __counter __RandomString __StringFromFile StringFromFile函数用于获取文本文件的值&#xff0c;一次读取一行 1、输入文件的全路径&#xff1a;填入文件路径 2、存储结果的变量名&#xff08;可选&#xff09; 3、Start file sequence …

绘制世界地图or中国地图

写在前面 在8月初,自己需要使用中国地图的图形,自己就此也查询相关的教程,自己也做一下小小总结,希望对自己和同学们有所帮助。 最终图形 这个系列从2022年开始,一直更新使用R语言分析数据及绘制精美图形。小杜的生信笔记主要分享小杜学习日常!如果,你对此感兴趣可以加…