本文已收录到 AndroidFamily,技术和职场问题,请关注公众号 [彭旭锐] 提问。

大家好,欢迎来到小彭的 LeetCode 周赛解题报告。

昨晚是 LeetCode 双周赛第 102 场,你参加了吗?这场比赛比较简单,拼的是板子手速,继上周掉大分后算是回了一口血 。


2618. 查询网格图中每一列的宽度(Easy)

简单模拟题,无需解释。

  • 模拟:$O(nm)$

2619. 一个数组所有前缀的分数(Medium)

简单动态规划题,简单到像模拟题。

  • 动态规划:$O(n)$

2620. 二叉树的堂兄弟节点 II(Medium)

思考过程:递归→DFS→BFS。由于堂兄弟节点都在同一层,发现 “递归地减少问题规模求解原问题” 和 DFS 都不好编码,而 BFS 更符合 “层” 的概念。往 BFS 方向思考后,容易找到解决方法。

  • BFS:$O(n)$

2621. 设计可以求最短路径的图类(Hard)

最近周赛的最短路问题非常多,印象中已经连续出现三次最短路问题。理解 Dijkstra 算法和 Floyd 算法的应用场景非常重要。

  • 朴素 Dijkstra:$O(m + q_1·n^2 + q_2)$
  • Dijkstra + 最小堆:$O(m + q_1·nlgm+q_2)$
  • Floyd:$O(m + n^3 + q_1 + q_2·n^2)$


2618. 查询网格图中每一列的宽度(Easy)

题目地址

https://leetcode.cn/problems/find-the-width-of-columns-of-a-grid/description/

题目描述

给你一个下标从 0 开始的 m x n 整数矩阵 grid 。矩阵中某一列的宽度是这一列数字的最大 字符串长度 。

  • 比方说,如果 grid = [[-10], [3], [12]] ,那么唯一一列的宽度是 3 ,因为 10 的字符串长度为 3 。

请你返回一个大小为 n 的整数数组 ans ,其中 ans[i] 是第 i 列的宽度。

一个有 len 个数位的整数 x ,如果是非负数,那么 字符串长度 为 len ,否则为 len + 1 。

题解(模拟)

class Solution {
fun findColumnWidth(grid: Array<IntArray>): IntArray {
val m = grid.size
val n = grid[0].size
val ret = IntArray(n)
for (column in 0 until n) {
for (row in 0 until m) {
ret[column] = Math.max(ret[column], "${grid[row][column]}".length)
}
}
return ret
}
}

复杂度分析:

  • 时间复杂度:$O(nm)$ 其中 $n$ 和 $m$ 为 grid 数组的行列大小,每个节点最多访问 1 次;
  • 空间复杂度:$O(1)$ 不考虑结果数组。

2619. 一个数组所有前缀的分数(Medium)

题目地址

https://leetcode.cn/problems/find-the-score-of-all-prefixes-of-an-array/description/

题目描述

定义一个数组 arr 的 转换数组 conver 为:

  • conver[i] = arr[i] + max(arr[0..i]),其中 max(arr[0..i]) 是满足 0 <= j <= i 的所有 arr[j] 中的最大值。

定义一个数组 arr 的 分数 为 arr 转换数组中所有元素的和。

给你一个下标从 0 开始长度为 n 的整数数组 nums ,请你返回一个长度为 n 的数组 **ans ,其中 ans[i]是前缀 nums[0..i] 的分数。

题解(动态规划)

简单动态规划题,容易发现递归关系:

  • conver[i] = max
  • dp[i] = dp[i-1] + conver[i]
class Solution {
fun findPrefixScore(nums: IntArray): LongArray {
val n = nums.size
val ret = LongArray(n)
// 初始状态
ret[0] = 2L * nums[0]
var maxNum = nums[0]
// DP
for (i in 1 until n) {
maxNum = Math.max(maxNum, nums[i])
ret[i] = ret[i - 1] + (0L + nums[i] + maxNum)
}
return ret
}
}

复杂度分析:

  • 时间复杂度:$O(n)$ 其中 $n$ 为 $arr$ 数组的长度,每个节点最多访问 1 次;
  • 空间复杂度:$O(1)$ 不考虑结果数组。

2620. 二叉树的堂兄弟节点 II(Medium)

题目地址

https://leetcode.cn/problems/cousins-in-binary-tree-ii/description/

题目描述

给你一棵二叉树的根 root ,请你将每个节点的值替换成该节点的所有 堂兄弟节点值的和 。

如果两个节点在树中有相同的深度且它们的父节点不同,那么它们互为 堂兄弟 。

请你返回修改值之后,树的根 **root **。

注意,一个节点的深度指的是从树根节点到这个节点经过的边数。

题解(BFS)

分析 1 - 递归:尝试分解左右子树求解问题,发现左右子树不独立,不再考虑此思路;

分析 2 - DFS / BFS:由于堂兄弟节点都在同一层,而 BFS 更符合 “层” 的概念,往 BFS 方向思考后,容易找到解决方法:在处理每一层的节点时,第一轮遍历先累计下一层节点的和,在第二轮遍历时更新下一层节点(取出自己和兄弟节点的值)。

/**
* Example:
* var ti = TreeNode(5)
* var v = ti.`val`
* Definition for a binary tree node.
* class TreeNode(var `val`: Int) {
* var left: TreeNode? = null
* var right: TreeNode? = null
* }
*/
class Solution {
fun replaceValueInTree(root: TreeNode?): TreeNode? {
if (null == root) return root
// BFS
val queue = LinkedList<TreeNode>()
queue.offer(root)
root.`val` = 0
while (!queue.isEmpty()) {
val size = queue.size
// 计算下一层的和
var nextLevelSum = 0
for (i in 0 until size) {
val node = queue[i]
if (null != node.left) nextLevelSum += node.left.`val`
if (null != node.right) nextLevelSum += node.right.`val`
}
for (count in 0 until size) {
val node = queue.poll()
// 减去非堂兄弟节点
var nextLevelSumWithoutNode = nextLevelSum
if (null != node.left) nextLevelSumWithoutNode -= node.left.`val`
if (null != node.right) nextLevelSumWithoutNode -= node.right.`val`
// 入队
if (null != node.left) {
queue.offer(node.left)
node.left.`val` = nextLevelSumWithoutNode
}
if (null != node.right) {
queue.offer(node.right)
node.right.`val` = nextLevelSumWithoutNode
}
}
}
return root
}
}

复杂度分析:

  • 时间复杂度:$O(n)$ 其中 n 为二叉树的节点总数,每个节点最多访问 2 次(含入队 1 次);
  • 空间复杂度:$O(n)$ BFS 队列空间。

相似题目:


2621. 设计可以求最短路径的图类(Hard)

题目地址

https://leetcode.cn/problems/design-graph-with-shortest-path-calculator/

题目描述

给你一个有 n 个节点的 有向带权 图,节点编号为 0 到 n - 1 。图中的初始边用数组 edges 表示,其中 edges[i] = [fromi, toi, edgeCosti] 表示从 fromi 到 toi 有一条代价为 edgeCosti 的边。

请你实现一个 Graph 类:

  • Graph(int n, int[][] edges) 初始化图有 n 个节点,并输入初始边。
  • addEdge(int[] edge) 向边集中添加一条边,其中 ****edge = [from, to, edgeCost] 。数据保证添加这条边之前对应的两个节点之间没有有向边。
  • int shortestPath(int node1, int node2) 返回从节点 node1 到 node2 的路径 最小 代价。如果路径不存在,返回 1 。一条路径的代价是路径中所有边代价之和。

问题分析

这道题勉强能算 Floyd 算法或 Dijkstra 算法的模板题,先回顾一下最短路问题解决方案:

  • Dijkstra 算法(单源正权最短路):

    • 本质上是贪心 + BFS;
    • 负权边会破坏贪心策略的选择,无法处理含负权问题;
    • 稀疏图小顶堆的写法更优,稠密图朴素写法更优。
  • Floyd 算法(多源汇正权最短路)
  • Bellman Ford 算法(单源负权最短路)
  • SPFA 算法(单源负权最短路)

由于这道题需要支持多次查询操作,而 Floyd 算法能够缓存最短路结果,理论上 Floyd 算法是更优的选择。不过,我们观察到题目的数据量非常非常小,所以朴素 Dijkstra 算法也能通过。

题解一(朴素 Dijkstra)

这道题的查询操作是求从一个源点到目标点的最短路径,并且这条路径上没有负权值,符合 Dijkstra 算法的应用场景,在处理添加边时,只需要动态的修改图数据结构。

Dijkstra 算法的本质是贪心 + BFS,我们需要将所有节点分为 2 类,在每一轮迭代中,我们从 “候选集” 中选择距离起点最短路长度最小的节点,由于该点不存在更优解,所以可以用该点来 “松弛” 相邻节点。

  • 1、确定集:已确定(从起点开始)到当前节点最短路径的节点;
  • 2、候选集:未确定(从起点开始)到当前节点最短路径的节点。

技巧:使用较大的整数 0x3F3F3F3F 代替整数最大值 Integer.MAX_VALUE 可以减少加法越界判断。

class Graph(val n: Int, edges: Array<IntArray>) {

    private val INF = 0x3F3F3F3F

    // 带权有向图(临接矩阵)
private val graph = Array(n) { IntArray(n) { INF } } init {
// i 自旋的路径长度
for (i in 0 until n) {
graph[i][i] = 0
}
// i 直达 j 的路径长度
for (edge in edges) {
addEdge(edge)
}
} fun addEdge(edge: IntArray) {
graph[edge[0]][edge[1]] = edge[2]
} fun shortestPath(node1: Int, node2: Int): Int {
// Dijkstra // 最短路
val dst = IntArray(n) { INF }
dst[node1] = 0
// 确定标记
val visited = BooleanArray(n)
// 迭代 n - 1 次
for (count in 0 until n - 1) {
// 寻找候选集中最短路长度最短的节点
var x = -1
for (i in 0 until n) {
if (!visited[i] && (-1 == x || dst[i] < dst[x])) x = i
}
// start 可达的节点都访问过 || 已确定 node1 -> node2 的最短路
if (-1 == x || dst[x] == INF || x == node2) break
visited[x] = true
// 松弛相邻节点
for (y in 0 until n) {
dst[y] = Math.min(dst[y], dst[x] + graph[x][y])
}
}
return if (INF == dst[node2]) -1 else dst[node2]
}
}

复杂度分析:

  • 时间复杂度:$O(m + q_1·n^2 + q_2)$ 其中 n 为节点数量,m 为边数量,$q_1$ 为查询次数,$q_2$ 为添加边次数。建图时间 O(m),每个节点访问 n 次;
  • 空间复杂度:$O(n^2 + n)$ 图空间 + 最短路数组

题解二(Dijkstra + 最小堆)

这道题是稠密图,朴素 Dijkstra 由于 Dijkstra + 最小堆。

朴素 Dijkstra 的每轮迭代中需要遍历 n 个节点寻找候选集中的最短路长度。事实上,这 n 个节点中有部分是 ”确定集“,有部分是远离起点的边缘节点,每一轮都遍历显得没有必要。我们使用小顶堆记录候选集中最近深度的节点。

class Graph(val n: Int, edges: Array<IntArray>) {

    private val INF = 0x3F3F3F3F

    // 带权有向图(临接矩阵)
private val graph = Array(n) { IntArray(n) { INF } } init {
// i 自旋的路径长度
for (i in 0 until n) {
graph[i][i] = 0
}
// i 直达 j 的路径长度
for (edge in edges) {
addEdge(edge)
}
} fun addEdge(edge: IntArray) {
graph[edge[0]][edge[1]] = edge[2]
} fun shortestPath(node1: Int, node2: Int): Int {
// Dijkstra + 最小堆 // 最短路
val dst = IntArray(n) { INF }
dst[node1] = 0
val heap = PriorityQueue<Int>() { i1, i2 ->
dst[i1] - dst[i2]
}
heap.offer(node1)
while (!heap.isEmpty()) {
// 使用 O(lgm) 时间找出最短路长度
var x = heap.poll()
// 松弛相邻节点
for (y in 0 until n) {
if (dst[x] + graph[x][y] < dst[y]) {
dst[y] = dst[x] + graph[x][y]
heap.offer(y)
}
}
}
return if (INF == dst[node2]) -1 else dst[node2]
}
}

复杂度分析:

  • 时间复杂度:$O(m + q_1·nlgm+q_2)$ 其中 n 为节点数量,m 为边数量,$q_1$ 为查询次数,$q_2$ 为添加边次数。建图时间 $O(m)$,每条边都会访问一次,每轮迭代取堆顶 O(lgm)。这道题边数大于点数,朴素写法更优。
  • 空间复杂度:$O(n^2 + n)$ 图空间 + 堆空间。

题解三(Floyd)

Fload 算法的本质是贪心 + BFS,我们需要三层循环枚举中转点 i、枚举起点 j 和枚举终点 k,如果 dst[i][k] + dst[k][j] < dst[i][j],则可以松弛 dst[i][j]。

这道题的另一个关键点在于支持调用 addEdge() 动态添加边,所以使用 Floyd 算法时要考虑如何更新存量图。

class Graph(val n: Int, edges: Array<IntArray>) {

    val INF = 0x3F3F3F3F

    // 路径长度(带权有向图)
val graph = Array(n) { IntArray(n) { INF } } init {
// i 自旋的路径长度
for (i in 0 until n) {
graph[i][i] = 0
}
// i 直达 j 的路径长度
for (edge in edges) {
graph[edge[0]][edge[1]] = edge[2]
}
// Floyd 算法
// 枚举中转点
for (k in 0 until n) {
// 枚举起点
for (i in 0 until n) {
// 枚举终点
for (j in 0 until n) {
// 比较 <i to j> 与 <i to p> + <p to j>
graph[i][j] = Math.min(graph[i][j], graph[i][k] + graph[k][j])
}
}
}
} fun addEdge(edge: IntArray) {
val (x, y, cost) = edge
// 直达
graph[x][y] = Math.min(graph[x][y], cost)
// 枚举中转点
for (k in intArrayOf(x, y)) {
// 枚举起点
for (i in 0 until n) {
// 枚举终点
for (j in 0 until n) {
// 比较 <i to j> 与 <i to k> + <k to j>
graph[i][j] = Math.min(graph[i][j], graph[i][k] + graph[k][j])
}
}
}
} fun shortestPath(node1: Int, node2: Int): Int {
return if (graph[node1][node2] == INF) -1 else graph[node1][node2]
}
}

复杂度分析:

  • 时间复杂度:$O(m + n^3 + q_1 + q_2·n^2)$ 其中 $n$ 为节点数量,$m$ 为边数量,$q_1$ 为查询次数,$q_2$ 为添加边次数。建图时间 $O(m + n^3)$,单次查询时间 $O(1)$,单次添加边时间 $O(n^2)$;
  • 空间复杂度:$O(n^2)$ 图空间。

相关题目:

近期周赛最短路问题:

LeetCode 双周赛 102,模拟 / BFS / Dijkstra / Floyd的更多相关文章

  1. [CSP-S模拟测试]:走格子(模拟+BFS+Dijkstra)

    题目描述 $CYJ$想找到他的小伙伴$FPJ$,$CYJ$和$FPJ$现在位于一个房间里,这个房间的布置可以看成一个$N$行$M$列的矩阵,矩阵内的每一个元素会是下列情况中的一种:$1.$障碍区域—这 ...

  2. leetcode 双周赛9 进击的骑士

    一个坐标可以从 -infinity 延伸到 +infinity 的 无限大的 棋盘上,你的 骑士 驻扎在坐标为 [0, 0] 的方格里. 骑士的走法和中国象棋中的马相似,走 “日” 字:即先向左(或右 ...

  3. LeetCode双周赛#36

    1604. 警告一小时内使用相同员工卡大于等于三次的人 题目链接 题意 给定两个字符串数组keyName和keyTime,分别表示名字为keytime[i]的人,在某一天内使用员工卡的时间(格式为24 ...

  4. Leetcode 双周赛#32 题解

    1540 K次操作转变字符串 #计数 题目链接 题意 给定两字符串\(s\)和\(t\),要求你在\(k\)次操作以内将字符串\(s\)转变为\(t\),其中第\(i\)次操作时,可选择如下操作: 选 ...

  5. leetcode 双周赛9 找出所有行中最小公共元素

    给你一个矩阵 mat,其中每一行的元素都已经按 递增 顺序排好了.请你帮忙找出在所有这些行中 最小的公共元素. 如果矩阵中没有这样的公共元素,就请返回 -1. 示例: 输入:mat = [[,,,,] ...

  6. [每日一题2020.06.16] leetcode双周赛T3 5423 找两个和为目标值且不重叠的子数组 DP, 前缀和

    题目链接 给你一个整数数组 arr 和一个整数值 target . 请你在 arr 中找 两个互不重叠的子数组 且它们的和都等于 target .可能会有多种方案,请你返回满足要求的两个子数组长度和的 ...

  7. LeetCode双周赛#35

    1589. 所有排列中的最大和 #差分 #贪心 题目链接 题意 给定整数数组nums,以及查询数组requests,其中requests[i] = [starti, endi] .第i个查询求 num ...

  8. LeetCode双周赛#34

    5492. 分割字符串的方案数 #组合公式 #乘法原理 #区间分割 题目链接 题意 给定01二进制串\(s\),可将\(s\)分割为三个非空 字符串\(s_1,s_2,s_3\),即(\(s_1+s_ ...

  9. LeetCode双周赛#33 题解

    5480. 可以到达所有点的最少点数目 #贪心 题目链接 题意 给定有向无环图,编号从0到n-1,一个边集数组edges(表示从某个顶点到另一顶点的有向边),现要找到最小的顶点集合,使得从这些点出发, ...

  10. 最短路问题(Bellman/Dijkstra/Floyd)

    最短路问题(Bellman/Dijkstra/Floyd) 寒假了,继续学习停滞了许久的算法.接着从图论开始看起,之前觉得超级难的最短路问题,经过两天的苦读,终于算是有所收获.把自己的理解记录下来,可 ...

随机推荐

  1. mysql主从同步复制

    主从同步原理 master记录数据操作 开启binlog日志 设置binlog日志格式 指定server_id slave启用俩个线程 slave_io:复制master主机binlog日志为文件里的 ...

  2. nodejs res常用的返回方式

    常用的返回方式有四种 res.json([status|body], [body])  以json的形式返回数据res.render(view [, locals] [, callback])  返回 ...

  3. 【转】【进程管理】Linux进程调度:调度时机

    转自:https://zhuanlan.zhihu.com/p/163728119 概述: 进程切换分为自愿(voluntary)和强制(involuntary)两种.通常自愿切换是指任务由于等待某种 ...

  4. kg打怪升级

    1.kaggle notebook容易断[continue部署] 2.换预训练模型[提交试试] 3.换fold次数

  5. vue几种插槽的使用方法

    参考文档:https://blog.csdn.net/weixin_49217200/article/details/118496525 参考文档:https://blog.csdn.net/ct52 ...

  6. el-table改变行高样式不生效的解决办法

    之前的效果是这样的,怎么设置也没用

  7. 在目标服务器Centos7上安装 GitLab Runner

    1.安装提示: 注意:如果你打算通过gitlab-ci,将项目部署到"目标服务器"上,那么这个GitLab Runner就要提前安装到这个"目标服务器"上 (这 ...

  8. Vue2使用axios,request.js和vue.config.js

    1.配置request.js,用来请求数据 import axios from 'axios' // 1:利用axios对象的方法create,创建一个axios实例 // 2:request就是ax ...

  9. 在app中如何使weib-view不铺满全屏,自适应页面

    // #ifdef APP-PLUS //自建webview var currentWebview = this.$scope.$getAppWebview(); var height = this. ...

  10. python def函数总结(格式、参数类型、传参方式、全局变量/局部变量、参数类型提示(Type Hints))

    简单无参函数 编写脚本test1.py def register_user(): #函数名称只使用小写字母和下划线 """docstring""&qu ...