LeetCode 周赛上分之旅 #49 再探内向基环树

news2024/11/26 11:42:57

⭐️ 本文已收录到 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 i < j < k i<j<k 的三位数;
  • 计算结果: ( n u m s [ i ] − n u m s [ j ] ) ∗ n u m s [ k ] (nums[i] - nums[j]) * nums[k] (nums[i]nums[j])nums[k]

思考实现:

  • T1. 有序三元组中的最大值 I 的数据量只有 100 100 100,枚举所有合法的 [ i , j , k ] [i, j, k] [i,j,k] 组合,时间复杂度是 O ( n 3 ) O(n^3) O(n3)
  • T2. 有序三元组中的最大值 II 的数据量有 1 0 5 10^5 105,我们需要思考更优解法。

思考优化:

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

  • 固定 j j j 为了让结果更大,应该找到 n u m s [ j ] nums[j] nums[j] 左边最大的 n u m s [ i ] nums[i] nums[i] 和右边最大的 n u m s [ k ] nums[k] nums[k] 组合,时间复杂度是 O ( n 2 ) O(n^2) O(n2)。我们也可以使用前后缀分解预处理出来,这样时间复杂度就是 O ( n ) O(n) O(n)
  • 固定 k k k 同理,固定 k k k 寻找应该找到左边使得 n u m s [ i ] − n u m s [ j ] nums[i] - nums[j] 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(n^3) O(n3)
  • 空间复杂度: O ( 1 ) O(1) O(1)

题解二(前后缀分解)

预处理出每个位置前后的最大值,再枚举 n u m s [ j ] nums[j] 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) O(n)
  • 空间复杂度: O ( n ) O(n) O(n)

题解三(线性遍历)

线性遍历 n u m s [ k ] nums[k] nums[k] 并记录 ( n u m s [ i ] − n u m s [ j ] ) (nums[i] - nums[j]) (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(n) O(n)
  • 空间复杂度: O ( 1 ) O(1) O(1)

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

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

问题分析

n u m s nums nums 数组的整体元素和为 s s s,考虑 t a r g e t target target 的两种情况:

  • 对于 t a r g e t target target 很小的情况(小于数组整体和 s s s):这是很简单的滑动窗口问题;
  • 对于 t a r g e t target target 较大的情况(大于等于数组的整体和 s s s):那么最小长度中一定包含整数倍的 s s s,以及某个 n u m s nums 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 ) O(n) O(n) 最大扫描 2 2 2 倍数组长度;
  • 空间复杂度:仅使用常量级别空间。

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

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

问题分析

初步分析:

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

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

图片不记得出处了~

思考实现:

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

题解(拓扑排序 + DFS)

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

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

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

在拓扑排序后,树链上节点的入度都是 0 0 0,因此入度大于 0 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 ) O(n) O(n) 拓扑排序和 DFS 都是线性时间;
  • 空间复杂度: O ( n ) O(n) O(n) 图空间和队列空间。

题解二(朴素 DFS)

思路参考小羊的题解。

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

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

  • 环上节点:刚好走过基环;
  • 树链节点:走过树链 + 基环。
  • 还有 1 1 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 ) O(n) O(n) DFS 都是线性时间;
  • 空间复杂度: O ( n ) O(n) O(n) 图空间和队列空间。

推荐阅读

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

  • LeetCode 单周赛第 364 场 · 前后缀分解结合单调栈的贡献问题
  • LeetCode 单周赛第 363 场 · 经典二分答案与质因数分解
  • LeetCode 双周赛第 114 场 · 一道简单的树上动态规划问题
  • LeetCode 双周赛第 113 场 · 精妙的 O(lgn) 扫描算法与树上 DP 问题

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

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

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

相关文章

1300*C. Coin Rows(枚举模拟)

解析&#xff1a; 两人都绝对聪明&#xff0c;Alice先走&#xff0c;尽量让Bob所能拿的分数最少&#xff0c;Alice有一次往下走的机会&#xff0c;剩余没走过的点正好分为两断断开的区域&#xff0c;所以Bob的最大分数要么在第一格向下或者在最后一列向下。 遍历区间&#xff0…

笔训day2

选择题 1、输出格式 此题与昨天的题类似&#xff0c;有“-”号时是左对齐&#xff0c;%-m.n m表示宽度&#xff0c;n表示左起取n位数。 2、常量指针和指针常量 //两种都是常量指针 const int *p1; int const *p2; //指针常量 int* const p3 3、字符数组和字符指针 4、函数…

国庆节看这里,有你意想不到的收货!(建议收藏)

计算机视觉研究院专栏 作者&#xff1a;Edison_G “国庆长假&#xff0c;每个人都安耐不住了&#xff0c;但是&#xff0c;在你静心、游玩的时候&#xff0c;还是可以阅读今天的分享&#xff0c;干货满满&#xff01; 公众号ID&#xff5c;ComputerVisionGzq 学习群&#xff5c…

基于SSM的医院住院综合服务管理系统的设计与实现

末尾获取源码 开发语言&#xff1a;Java Java开发工具&#xff1a;JDK1.8 后端框架&#xff1a;SSM 前端&#xff1a;采用Vue技术开发 数据库&#xff1a;MySQL5.7和Navicat管理工具结合 服务器&#xff1a;Tomcat8.5 开发软件&#xff1a;IDEA / Eclipse 是否Maven项目&#x…

数据结构与算法(C语言版)P9---哈夫曼树

1、哈夫曼树的基本概念 &#xff08;1&#xff09;路径&#xff1a;从树中一个结点到另一个结点之间的__分支__构成这两个结点间的路径。 &#xff08;2&#xff09;__结点的路径长度&#xff1a;__两结点间路径上的分支树。 练习&#xff1a;计算下面二叉树结点之间的路径长…

1.7.C++项目:仿muduo库实现并发服务器之Poller模块的设计

项目完整在&#xff1a; 文章目录 一、Poller模块&#xff1a;描述符IO事件监控模块二、提供的功能三、实现思想&#xff08;一&#xff09;功能&#xff08;二&#xff09;意义&#xff08;三&#xff09;功能设计 四、封装思想五、代码&#xff08;一&#xff09;框架&#…

基于微信小程序的懒人美食帮外卖订餐设计与实现(亮点:多角色的订餐系统、最贴近现实的小程序)

文章目录 前言系统主要功能&#xff1a;具体实现截图论文参考详细视频演示为什么选择我自己的网站自己的小程序&#xff08;小蔡coding&#xff09;有保障的售后福利 代码参考源码获取 前言 &#x1f497;博主介绍&#xff1a;✌全网粉丝10W,CSDN特邀作者、博客专家、CSDN新星计…

CH347读取MPU6050传感器数据和显示

MPU6050 是一款集成了六轴加速度计和陀螺仪的微电子机械系统&#xff08;MEMS&#xff09;传感器。它由 InvenSense&#xff08;现为 TDK&#xff09; 公司开发&#xff0c;是一种广泛应用于姿态估计、运动追踪和稳定控制等领域的常用传感器。 MPU6050 具有以下主要特点和技术…

1、【开始】【简介】Qlib:量化平台

【简介】1、Qlib:量化平台 简介框架简介 Qlib是一个面向AI的量化投资平台,旨在实现AI技术在量化投资中的潜力,赋能研究,并创造价值。 通过Qlib,用户可以轻松利用他们的想法来创建更好的量化投资策略。 框架 在模块层,Qlib 是由上述组件组成的平台。这些组件被设计为低耦…

ssm+vue的图书管理系统(有报告)。Javaee项目,ssm vue前后端分离项目。

演示视频&#xff1a; ssmvue的图书管理系统(有报告)。Javaee项目&#xff0c;ssm vue前后端分离项目。 项目介绍&#xff1a; 采用M&#xff08;model&#xff09;V&#xff08;view&#xff09;C&#xff08;controller&#xff09;三层体系结构&#xff0c;通过Spring Spr…

正点原子嵌入式linux驱动开发——TF-A移植

经过了之前的学习&#xff0c;除了TF-A的详细启动流程仍待更新&#xff0c;TF-A的使用和其对应的大致启动流程已经进行过了学习。但是当我们实际做产品时&#xff0c;硬件平台肯定会和ST官方的有区别&#xff0c;比如DDR容量会改变&#xff0c;自己的硬件没有使用到官方EVK开发…

1.HTML-标题排版

1.Vscode快速生成基本代码结构 !enter <!DOCTYPE html> <html lang"en"> <head><meta charset"UTF-8"><meta http-equiv"X-UA-Compatible" content"IEedge"><meta name"viewport" conte…

Acwing 908. 最大不相交区间数量

Acwing 908. 最大不相交区间数量 知识点题目描述思路讲解代码展示 知识点 贪心 题目描述 思路讲解 代码展示 #include <iostream> #include <algorithm>using namespace std;const int N 100010;int n;struct Range {int l, r;bool operator<(const Range …

【爬虫】用wget命令爬虫的简易教程

文章目录 1. 获取登录的请求2. 用postman模拟登录请求3. 用wget模拟登录请求并保存cookie4. 开始爬取网站5. 查看爬取结果6. 网站爬虫简易教程 爬取需要登录的网站的资源 背景&#xff1a;对于一些网站需要使用用户名和密码登录并且使用了https&#xff0c;我们如果不通过凭证将…

Arcgis提取玉米种植地分布,并以此为掩膜提取遥感影像

Arcgis提取玉米种植地分布上&#xff0c;并以此为掩膜提取遥感影像 一、问题描述 因为之前反演是整个研究区&#xff0c;然而土地利用类型有很多类&#xff0c;只在农田或者植被上进行反演&#xff0c;需要去除水体、建筑等其他类型&#xff0c;如何处理得到下图中只有耕地类…

世界前沿技术发展报告2023《世界信息技术发展报告》(六)网络与通信技术

&#xff08;六&#xff09;网络与通信技术 1. 概述2. 5G与光通讯2.1 美国研究人员利用电磁拓扑绝缘体使5G频谱带宽翻倍2.2 日本东京工业大学推出可接入5G网络的高频收发器2.3 美国得克萨斯农工大学通过波束管理改进5G毫米波通信2.4 联发科完成全球首次5G NTN卫星手机连线测试2…

springmvc-国际化中英文切换文件上传下载

1. 国际化 1.1 介绍 国际化(internationalization)&#xff0c; 简称国际化。一个产品支持国际化是指产品在无需做大的改变就能够适应不同的语言和地区的能力。i18n是指是一种让软件在开发阶段就支持多种语言的技术。 1.2 java.util.Locale 该类对象表示了特定的地理&#…

【算法学习】-【双指针】-【复写零】

LeetCode原题链接&#xff1a;1089. 复写零 下面是题目描述&#xff1a; 给你一个长度固定的整数数组 arr &#xff0c;请你将该数组中出现的每个零都复写一遍&#xff0c;并将其余的元素向右平移。 注意&#xff1a;请不要在超过该数组长度的位置写入元素。请对输入的数组 …

【word】从正文开始设置页码

在写报告的时候&#xff0c;会要求有封面和目录&#xff0c;各占一页。正文从第3页开始&#xff0c;页码从正文开始设置 word是新建的 分出三节&#xff08;封面、目录、正文&#xff09; 布局--->分割符--->分节符--->下一页 这样就能将word分为3节&#xff0c;分…

『RSSHub』搭建部署指南

前言 相信各位对推荐算法已经很熟悉了&#xff0c;平台基于推荐算法不断推送我们感兴趣的信息&#xff0c;但是身处推荐算法中心&#xff0c;有时我们可能感觉视野越来越闭塞&#xff0c;原来节约我们时间的推荐系统&#xff0c;这时却成了困住我们的信息茧房 那么也许 RSS&a…