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

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

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

LeetCode 周赛 365

T1. 有序三元组中的最大值 I(Easy)

  • 标签:模拟、前后缀分解、线性遍历

T2. 有序三元组中的最大值 II(Medium)

  • 标签:模拟、前后缀分解、线性遍历

T3. 无限数组的最短子数组(Medium)

  • 标签:滑动窗口

T4. 有向图访问计数(Hard)

  • 标签:内向基环树、拓扑排序、DFS


T1. 有序三元组中的最大值 I(Easy)

https://leetcode.cn/problems/maximum-value-of-an-ordered-triplet-i/description/

同 T2。


T2. 有序三元组中的最大值 II(Medium)

https://leetcode.cn/problems/maximum-value-of-an-ordered-triplet-ii/description/

问题分析

初步分析:

  • 问题目标: 构造满足条件的合法方案,使得计算结果最大;
  • 问题条件: 数组下标满足 $i < j < k$ 的三位数;
  • 计算结果: $(nums[i] - nums[j]) * nums[k]$。

思考实现:

思考优化:

为了使得计算结果尽可能大,显然应该让乘法的左右两部分尽可能大。对于存在多个变量的问题,一个重要的技巧是 「固定一个,思考另一个」 ,这就容易多了。

  • 固定 $j$: 为了让结果更大,应该找到 $nums[j]$ 左边最大的 $nums[i]$ 和右边最大的 $nums[k]$ 组合,时间复杂度是 $O(n^2)$。我们也可以使用前后缀分解预处理出来,这样时间复杂度就是 $O(n)$;
  • 固定 $k$: 同理,固定 $k$ 寻找应该找到左边使得 $nums[i] - nums[j]$ 最大的方案,这可以实现线性时间和常量空间。

题解一(枚举)

枚举所有方案,记录最优解。

class Solution {
fun maximumTripletValue(nums: IntArray): Long {
var ret = 0L
val n = nums.size
for (i in 0 until n) {
for (j in i + 1 until n) {
for (k in j + 1 until n) {
ret = max(ret, 1L * (nums[i] - nums[j]) * nums[k])
}
}
}
return ret
}
}

复杂度分析:

  • 时间复杂度:$O(n^3)$
  • 空间复杂度:$O(1)$

题解二(前后缀分解)

预处理出每个位置前后的最大值,再枚举 $nums[j]$ 记录最优解。

class Solution {
fun maximumTripletValue(nums: IntArray): Long {
val n = nums.size
val preMax = IntArray(n)
var sufMax = IntArray(n)
for (i in 1 until n) {
preMax[i] = max(preMax[i - 1], nums[i - 1])
}
for (i in n - 2 downTo 0) {
sufMax[i] = max(sufMax[i + 1], nums[i + 1])
}
return max(0, (1 .. n - 2).maxOf { 1L * (preMax[it] - nums[it]) * sufMax[it] })
}
}

复杂度分析:

  • 时间复杂度:$O(n)$
  • 空间复杂度:$O(n)$

题解三(线性遍历)

线性遍历 $nums[k]$ 并记录 $(nums[i] - nums[j])$ 的最大值,记录最优解。

class Solution {
fun maximumTripletValue(nums: IntArray): Long {
val n = nums.size
var ret = 0L
var maxDelta = 0
var maxI = 0
for (e in nums) {
ret = max(ret, 1L * maxDelta * e)
maxDelta = max(maxDelta, maxI - e)
maxI = max(maxI, e)
}
return ret
}
}
class Solution:
def maximumTripletValue(self, nums: List[int]) -> int:
ret = maxDelta = maxI = 0
for e in nums:
ret = max(ret, maxDelta * e)
maxDelta = max(maxDelta, maxI - e)
maxI = max(maxI, e)
return ret
class Solution {
public:
long long maximumTripletValue(vector<int> &nums) {
long long ret = 0;
int max_delta = 0, max_i = 0;
for (int e : nums) {
ret = max(ret, (long long) max_delta * e);
max_delta = max(max_delta, max_i - e);
max_i = max(max_i, e);
}
return ret;
}
};

复杂度分析:

  • 时间复杂度:$O(n)$
  • 空间复杂度:$O(1)$

T3. 无限数组的最短子数组(Medium)

https://leetcode.cn/problems/minimum-size-subarray-in-infinite-array/description/

问题分析

令 $nums$ 数组的整体元素和为 $s$,考虑 $target$ 的两种情况:

  • 对于 $target$ 很小的情况(小于数组整体和 $s$):这是很简单的滑动窗口问题;
  • 对于 $target$ 较大的情况(大于等于数组的整体和 $s$):那么最小长度中一定包含整数倍的 $s$,以及某个 $nums$ 的子数组。
class Solution {
fun minSizeSubarray(nums: IntArray, t: Int): Int {
val n = nums.size
val s = nums.sum()
val k = t % s
// 同向双指针
var left = 0
var sum = 0
var len = n
for (right in 0 until 2 * n) {
sum += nums[right % n]
while (sum > k) {
sum -= nums[left % n]
left ++
}
if (sum == k) len = min(len, right - left + 1)
}
return if (len == n) -1 else n * (t / s) + len
}
}

复杂度分析:

  • 时间复杂度:$O(n)$ 最大扫描 $2$ 倍数组长度;
  • 空间复杂度:仅使用常量级别空间。

T4. 有向图访问计数(Hard)

https://leetcode.cn/problems/count-visited-nodes-in-a-directed-graph/description/

问题分析

初步分析:

对于 $n$ 个点 $n$ 条边的有向弱连通图,图中每个点的出度都是 $1$,因此它是一棵 「内向基环树」。那么,对于每个点有 $2$ 种情况:

  • 环上节点:绕环行走一圈后就会回到当前位置,因此最长访问路径就是环长;
  • 树链节点:那么从树链走到环上后也可以绕环行走一圈,因此最长访问路径就是走到环的路径 + 环长。

图片不记得出处了~

思考实现:

  • 只有一个连通分量的情况: 那么问题就相对简单,我们用拓扑排序剪去树链,并记录链上节点的深度(到环上的距离),最后剩下的部分就是基环;
  • 有多个连通分量的情况: 我们需要枚举每个连通分量的基环,再将基环的长度累加到该连通分量的每个节点。

题解(拓扑排序 + DFS)

  • 第一个问题:将基环的长度累加到该连通分量的每个节点

拓扑排序减去树链很容易实现,考虑到我们这道题在找到基环后需要反向遍历树链,因此我们考虑构造反向图(外向基环树);

  • 第二个问题:找到基环长度

在拓扑排序后,树链上节点的入度都是 $0$,因此入度大于 $0$ 的节点就位于基环上。枚举未访问的基环节点走 DFS,就可以找到该连通分量的基环。

class Solution {
fun countVisitedNodes(edges: List<Int>): IntArray {
// 内向基环树
val n = edges.size
val degree = IntArray(n)
val graph = Array(n) { LinkedList<Int>() }
for ((x,y) in edges.withIndex()) {
graph[y].add(x)
degree[y]++ // 入度
}
// 拓扑排序
val ret = IntArray(n)
var queue = LinkedList<Int>()
for (i in 0 until n) {
if (0 == degree[i]) queue.offer(i)
}
while(!queue.isEmpty()) {
val x = queue.poll()
val y = edges[x]
if (0 == -- degree[y]) queue.offer(y)
} // 反向 DFS
fun rdfs(i: Int, depth: Int) {
for (to in graph[i]) {
if (degree[to] == -1) continue
ret[to] = depth
rdfs(to, depth + 1)
}
} // 枚举连通分量
for (i in 0 until n) {
if (degree[i] <= 0) continue
val ring = LinkedList<Int>()
var x = i
while (true) {
degree[x] = -1
ring.add(x)
x = edges[x]
if (x == i) break
}
for (e in ring) {
ret[e] = ring.size
rdfs(e, ring.size + 1)
}
}
return ret
}
}

复杂度分析:

  • 时间复杂度:$O(n)$ 拓扑排序和 DFS 都是线性时间;
  • 空间复杂度:$O(n)$ 图空间和队列空间。

题解二(朴素 DFS)

思路参考小羊的题解。

我们发现这道题的核心在于 「找到每个基环的节点」 ,除了拓扑排序剪枝外,对于内向基环树来,从任何一个节点走 DFS 走到的最后一个节点一定是基环上的节点。

在细节上,对于每个未访问过的节点走 DFS 的结果会存在 $3$ 种情况:

  • 环上节点:刚好走过基环;
  • 树链节点:走过树链 + 基环。
  • 还有 $1$ 种情况:DFS 起点是从树链的末端走的,而前面树链的部分和基环都被走过,此时 DFS 终点就不一定是基环节点了。这种情况就同理从终点直接反向遍历就好了,等于说省略了处理基环的步骤。
class Solution {
fun countVisitedNodes(edges: List<Int>): IntArray {
val n = edges.size
val ret = IntArray(n)
val visit = BooleanArray(n)
for (i in 0 until n) {
if (visit[i]) continue
// DFS
val link = LinkedList<Int>()
var x = i
while (!visit[x]) {
visit[x] = true
link.add(x)
x = edges[x]
}
if (ret[x] == 0) {
val depth = link.size - link.indexOf(x) // (此时 x 位于基环入口)
repeat(depth) {
ret[link.pollLast()] = depth
}
}
var depth = ret[x]
while (!link.isEmpty()) {
ret[link.pollLast()] = ++depth
}
}
return ret
}
}

复杂度分析:

  • 时间复杂度:$O(n)$ DFS 都是线性时间;
  • 空间复杂度:$O(n)$ 图空间和队列空间。

推荐阅读

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

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

LeetCode 周赛上分之旅 #49 再探内向基环树的更多相关文章

  1. 集合类再探:不可变类的好处,Collector接口详解,使用内部迭代

    集合类再探 注:本文使用的pom依赖见文末. 集合类的基础 - Iterable.Iterator java语言层面支持对实现了Iterable接口的对象使用for-each语句.Iterator可以 ...

  2. 刷爆 LeetCode 周赛 337,位掩码/回溯/同余/分桶/动态规划·打家劫舍/贪心

    本文已收录到 AndroidFamily,技术和职场问题,请关注公众号 [彭旭锐] 提问. 大家好,我是小彭. 上周末是 LeetCode 第 337 场周赛,你参加了吗?这场周赛第三题有点放水,如果 ...

  3. LeetCode 周赛 342(2023/04/23)容斥原理、计数排序、滑动窗口、子数组 GCB

    本文已收录到 AndroidFamily,技术和职场问题,请关注公众号 [彭旭锐] 提问. 大家好,我是小彭. 前天刚举办 2023 年力扣杯个人 SOLO 赛,昨天周赛就出了一场 Easy - Ea ...

  4. 【再探backbone 02】集合-Collection

    前言 昨天我们一起学习了backbone的model,我个人对backbone的熟悉程度提高了,但是也发现一个严重的问题!!! 我平时压根没有用到model这块的东西,事实上我只用到了view,所以昨 ...

  5. 再探jQuery

    再探jQuery 前言:在使用jQuery的时候发现一些知识点记得并不牢固,因此希望通过总结知识点加深对jQuery的应用,也希望和各位博友共同分享. jQuery是一个JavaScript库,它极大 ...

  6. [老老实实学WCF] 第五篇 再探通信--ClientBase

    老老实实学WCF 第五篇 再探通信--ClientBase 在上一篇中,我们抛开了服务引用和元数据交换,在客户端中手动添加了元数据代码,并利用通道工厂ChannelFactory<>类创 ...

  7. Spark Streaming揭秘 Day7 再探Job Scheduler

    Spark Streaming揭秘 Day7 再探Job Scheduler 今天,我们对Job Scheduler再进一步深入一下,对一些更加细节的源码进行分析. Job Scheduler启动 在 ...

  8. 再探ASP.NET 5(转载)

    就在最近一段时间,微软又有大动作了,在IDE方面除了给我们发布了Viausl Studio 2013 社区版还发布了全新的Visual Studio 2015 Preview. Visual Stud ...

  9. 再探java基础——break和continue的用法

    再探java基础——break和continue的用法 break break可用于循环和switch...case...语句中. 用于switch...case中: 执行完满足case条件的内容内后 ...

  10. 第四节:SignalR灵魂所在Hub模型及再探聊天室样例

    一. 整体介绍 本节:开始介绍SignalR另外一种通讯模型Hub(中心模型,或者叫集线器模型),它是一种RPC模式,允许客户端和服务器端各自自定义方法并且相互调用,对开发者来说相当友好. 该节包括的 ...

随机推荐

  1. 【Ubuntu22.04】配置静态IP地址和FTP服务

    ## 一.配置静态IP 1. 使用命令`ip a`查看当前网卡名称,Ubuntu22.04默认网卡为ens33: ![](https://img2023.cnblogs.com/blog/308121 ...

  2. CKS 考试题整理 (03)-kube-bench 修复不安全项

    Context 针对 kubeadm 创建的 cluster 运行 CIS 基准测试工具时,发现了多个必须立即解决的问题. Task 通过配置修复所有问题并重新启动受影响的组件以确保新的设置生效. 修 ...

  3. 【python基础】文件-文件路径

    1.文件路径 我们发现不管是写入还是写出操作,我们提供的都是文件名,其实这里准确说应该是文件路径.当我们简单把文件名传递给open函数时,Python将在当前执行程序的文件所在的目录中查找文件名所代表 ...

  4. 兰姆达 x AnayticDB 降本30%的数据湖最佳实践

    1. 客户介绍 上海兰姆达数据科技有限公司(简称"兰姆达数据")是一家提供卓越的数据科学软件产品和解决方案的初创高科技公司.兰姆达核心团队专注于大数据,机器学习算法和精准营销Saa ...

  5. 深入JS——理解闭包可以看作是某种意义上的重生

    JS中有一个非常重要但又难以完全掌握的概念,那就是闭包.很多JS程序员自以为已经掌握了闭包,但实质上是一知半解,就像"JS中万物皆为对象"这个常见的错误说法一样,很多前端开发者到现 ...

  6. 调用内部或私有方法的N种方法

    非公开的类型或者方法被"隐藏"在程序集内部,本就不希望从外部访问,但是有时候调用一个内部或者私有方法可能是唯一的"救命稻草",这篇文章列出了几种具体的实现方式. ...

  7. 即构推出低延迟直播产品L3,可将直播延迟降到1s

    近日,全球云通讯服务提供商ZEGO即构科技推出低延迟直播产品Low-Latency Live,简称L3.这款产品对传统CDN直播中"延迟较大.弱网抗性差.观众端内容不同步"等问题进 ...

  8. Hexo、Typecho博客添加旅行足迹网页

    本文部署的足迹地图,地址如下: http://www.aomanhao.top/index.php/archives/183/ jVectorMap JVectorMap 是一个优秀的.兼容性强的 j ...

  9. C#解析匿名JSON数据

    C#解析匿名JSON数据 使用工具:Newtonsoft.Json 使用方式: //模拟数据 var jsonData = JsonConvert.SerializeObject(new { Name ...

  10. 你不知道的 HTTP Referer

    前言 上周突然发现自己的自己站点的图片全都403了,之前还是好好的,图片咋就全都访问不了呢?由于我每次发文章都是先发了掘金,然后再从掘金拷贝到我自己的站点,这样我就不用在自己的站点去上传图片了,非常方 ...