LeetCode 周赛 346(2023/05/21)仅 68 人 AK 的最短路问题

news2024/10/6 20:26:35

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

  • LeetCode 单周赛第 345 场 · 体验一题多解的算法之美

单周赛 345 概览

T1. 删除子串后的字符串最小长度(Easy)

标签:栈

T2. 字典序最小回文串(Medium)

标签:贪心、双指针

T3. 求一个整数的惩罚数(Medium)

标签:回溯、状态压缩、前缀和

T4. 修改图中的边权(Hard)

标签:贪心、最短路


T1. 删除子串后的字符串最小长度(Easy)

https://leetcode.cn/problems/minimum-string-length-after-removing-substrings/

题解(栈)

使用栈模拟扫描过程,当扫描到 DB 时检查栈顶元素,最后栈内剩余的元素个数就是无法消除的最小长度:

class Solution {
    fun minLength(s: String): Int {
        val stack = ArrayDeque<Char>()
        for (c in s) {
            if (c == 'D' && stack.isNotEmpty() && stack.peek() == 'C') stack.pop()
            else if (c == 'B' && stack.isNotEmpty() && stack.peek() == 'A') stack.pop()
            else stack.push(c)
        }
        return stack.size
    }
}

复杂度分析:

  • 时间复杂度: O ( n ) O(n) O(n) 其中 n 为 s 字符串的长度;
  • 空间复杂度: O ( n ) O(n) O(n) 栈空间。

T2. 字典序最小回文串(Medium)

https://leetcode.cn/problems/lexicographically-smallest-palindrome/

题解(贪心)

贪心思路:当对称位置不相等时,只需要将其中一个位置修改到与另一个位置相同时,得到的操作次数是最少的:

class Solution {
    fun makeSmallestPalindrome(s: String): String {
        val arr = s.toCharArray()
        val n = s.length
        // 判断回文串写法
        for (i in 0 until n / 2) {
            val j = n - 1 - i
            if(arr[i] != arr[j]) {
                val temp = if(arr[i] < arr[j]) arr[i] else arr[j]
                arr[i] = temp
                arr[j] = temp
            }
        }
        return String(arr)
    }
}

复杂度分析:

  • 时间复杂度: O ( n ) O(n) O(n) 其中 n 为 s 字符串的长度;
  • 空间复杂度: O ( n ) O(n) O(n) 字符数组空间。

T3. 求一个整数的惩罚数(Medium)

https://leetcode.cn/problems/find-the-punishment-number-of-an-integer/

题解一(子集型回溯)

枚举每个数,使用子集型回溯检查是否存在满足条件的切分方案:

class Solution {
    fun punishmentNumber(n: Int): Int {
        if (n <= 3) return 1
        var ret = 0
        for (x in 4 .. n) {
            val target = x * x
            if (backTrack("$target", 0, x)) ret += target
        }
        return ret + 1 /* 1 满足条件 */
    }

    // 子集型回溯
    private fun backTrack(str : String, i : Int, target : Int) : Boolean {
        if (i == str.length) return target == 0
        var cur = 0
        for (to in i until str.length) {
            cur = cur * 10 + (str[to] - '0')
            if (backTrack(str, to + 1, target - cur)) return true
        }
        return false
    }
}

复杂度分析:

  • 时间复杂度: O ( n 2 ) O(n^2) O(n2) 每个数字 i 转字符串后的长度为 l o g i log_i logi,而枚举长度为 l o g i log_i logi 的字符串的切分方案后 2 l o g i 2^{log_i} 2logi = i 种方案,因此整体的时间复杂度是 O ( n 2 ) O(n^2) O(n2)
  • 空间复杂度: O ( l g n ) O(lgn) O(lgn) 递归栈空间。

题解二(状态压缩)

由于数字的长度小于 32,我们可以用 int 表示所有切分方案,再检查是否存在满足条件的切分方案:

class Solution {
    fun punishmentNumber(n: Int): Int {
        if (n <= 3) return 1
        var ret = 0
        for (x in 4 .. n) {
            val target = x * x
            if (check("$target", x)) ret += target
        }
        return ret + 1 /* 1 满足条件 */
    }

    // 状态压缩
    private fun check(str : String, target : Int) : Boolean {
        val m = str.length
        val upper = (1 shl m) - 1
        for (k in 1 .. upper) {
            var last = 0
            var sum = 0
            for (i in 0 until m) {
                val cur = str[i] - '0'
                if (k and (1 shl i) != 0) {
                    // 拆
                    sum += last
                    last = cur
                } else{
                    // 不拆
                    last = last * 10 + cur
                }
            }
            if (sum + last == target) return true
        }
        return false
    }
}

复杂度分析:

  • 时间复杂度:同上;
  • 空间复杂度: O ( 1 ) O(1) O(1) 仅使用常量级别空间。

题解三(预处理 + 前缀和)

题解一和题解二在多个测试用例间会重复计算相同数字的切分方案,我们可以预处理 1 - 1000 中所有满足条件的数平方,并维护前缀和数组:

class Solution {

    companion object {
        private val U = 1000
        private val preSum = IntArray(U + 1)
        init {
            for (x in 4 .. U) {
                val target = x * x
                if (check("$target", x)) preSum[x] += target
                preSum[x] += preSum[x - 1]
            }
        }

        // 状态压缩
        private fun check(str : String, target : Int) : Boolean {
        }
    }

    fun punishmentNumber(n: Int): Int {
        return preSum[n] + 1
    }
}

复杂度分析:

  • 时间复杂度: O ( U 2 ) O(U^2) O(U2) 其中 U 是数据大小上界;
  • 空间复杂度: O ( U ) O(U) O(U) 前缀和数组空间。

T4. 修改图中的边权(Hard)

https://leetcode.cn/problems/modify-graph-edge-weights/submissions/434224996/

LeetCode 少有的难题,排进历史 Top 10 没问题吧?

问题无解的情况:

  • 1、假设将所有负权边设置为 INF(2*10^9)时的最短路长度 dis < target(不论是否经过负权边),由于无法继续增大边权来增大最短路长度,因此问题无解;
  • 2、假设将所有负权边设置为 1 时的最短路长度 dis > target(不论是否经过负权边),由于继续增大边权最短路不可能变小,因此问题无解。

错误的思路:

先把所有负权边设置为 1,再跑 Dijkstra 最短路,如果最短路长度 dis < target,那么将其中一条负权边继续增大 “target - dis”,就能是该路径的长度恰好为 target。然而,由于增加权重后最短路长度有可能变化,所以这个思路不能保证正确性。

正确的思路:

  • 1、先把所有负权边改为 1 跑 Dijkstra 最短路,计算出起点到终点的最短路长度。同时,如果该长度 dis > target,则问题无解;如果该长度 dis == target,则直接返回;如果该长度 dis < target,则需要补全。
  • 2、问题的关键在于,按什么顺序修改,以及修改到什么值。
    • 顺序:利用 Dijkstra 最短路算法每次使用「确定集」中最短路长度最短的节点去松弛其他点的时机,由于修改该点不会影响已确定路径,因此这是一个不错的时机;
    • 修改到什么值:需要满足 dis[0][x] + w + dis[y][e] = target,那么有 w = target - dis[0][x] - (dis[0][e] - dis[0][y]) = delta - dis[0][x] + dis[0][y]
  • 3、虽然修改后最短路不一定经过 w,但由于不断的使用最短路长度最短的节点,因此最终总能修改成功,除非修改后最短路依然小于 target(例如存在直接从 s 到 e 的边)
  • 4、最后,将未修改的边增加到 INF。
class Solution {

    private val INF = 1e9.toInt()

    fun modifiedGraphEdges(n: Int, edges: Array<IntArray>, source: Int, destination: Int, target: Int): Array<IntArray> {
        if (source !in 0 .. n - 1 || destination !in 0 .. n - 1) return edges
        if (source == destination || edges.isNullOrEmpty()) return edges
        // 建图(领接表,节点号 + 边号方便修改边权)
        val graph = Array(n) { ArrayList<IntArray>() }
        for ((i, edge) in edges.withIndex()) {
            graph[edge[0]].add(intArrayOf(edge[1], i))
            graph[edge[1]].add(intArrayOf(edge[0], i))
        }
        // 第一轮最短路
        val originDis = dijkstra1(graph, edges, source, destination)
        if (originDis[destination] > target) return emptyArray() // 无解
        // 第二轮最短路
        val delta = target - originDis[destination] // 需要补全的最短路
        val dis = dijkstra2(graph, edges, source, destination, delta, originDis)
        if (dis[destination] < target) return emptyArray() // 无解
        // 修改剩余边
        for (edge in edges) {
            if (edge[2] == -1) edge[2] = INF
        }
        return edges
    }

    // return:将 -1 视为 1,并计算从起点到终点的最短路
    private fun dijkstra1(graph:Array<ArrayList<IntArray>>, edges: Array<IntArray>, source :Int, destination:Int) : IntArray {
        val n = graph.size
        val visit = BooleanArray(n)
        val dis = IntArray(n) { INF }
        dis[source] = 0
        while (true) {
            // 寻找最短路长度最短的节点
            var x = -1
            for (i in 0 until n) {
                if (visit[i]) continue
                if (-1 == x || dis[i] < dis[x]) x = i
            }
            if (x == destination) break
            visit[x] = true // 标记
            // 松弛相邻边
            for (to in graph[x]) {
                var w = edges[to[1]][2]
                if (-1 == w) w = 1 // 视为 1
                if (dis[x] + w < dis[to[0]]) dis[to[0]] = dis[x] + w
            }
        }
        return dis
    }

    // 补全
    private fun dijkstra2(graph:Array<ArrayList<IntArray>>, edges: Array<IntArray>, source :Int, destination:Int, delta: Int, originDis:IntArray /* 首轮计算的最短路 */) : IntArray {
        val n = graph.size
        val visit = BooleanArray(n)
        val dis = IntArray(n) { INF }
        dis[source] = 0
        while (true) {
            // 寻找最短路长度最短的节点
            var x = -1
            for (i in 0 until n) {
                if (visit[i]) continue
                if (-1 == x || dis[i] < dis[x]) x = i
            }
            if (x == destination) break
            visit[x] = true // 标记
            // 松弛相邻边
            for (to in graph[x]) {
                var w = edges[to[1]][2]
                if (-1 == w) {
                    // 补全(两次 Dijkstra 只修改这里)
                    w = Math.max(delta - dis[x] + originDis[to[0]], 1) // 题目要求至少修改到 1
                    if (w >= 1) edges[to[1]][2] = w
                }
                if (dis[x] + w < dis[to[0]]) dis[to[0]] = dis[x] + w
            }
        }
        return dis
    }
}

复杂度分析:

  • 时间复杂度: O ( n 2 ) O(n^2) O(n2) 两轮最短路算法;
  • 空间复杂度: O ( m ) O(m) O(m) 图空间。

往期回顾

  • LeetCode 单周赛第 345 场 · 体验一题多解的算法之美
  • LeetCode 单周赛第 344 场 · 手写递归函数的通用套路
  • LeetCode 双周赛第 104 场 · 流水的动态规划,铁打的结构化思考
  • LeetCode 双周赛第 103 场 · 区间求和的树状数组经典应用

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

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

相关文章

Sui基金正在招聘亚太地区市场经理,期待您的加入

Sui基金会致力于支持Sui网络的开发、增长和推广使用。Sui是基于第一原理重新设计和构建而成的L1公有链&#xff0c;旨在为创作者和开发者能够构建从游戏到金融的独特Web3体验。 Sui基金会三大去中心化原则&#xff1a;拥抱透明且公平的竞争环境、公开沟通以建立信任的文化&…

传染病学模型 | Matlab实现SIR传染病学模型 (SIR Epidemic Model)

文章目录 效果一览基本描述模型介绍程序设计参考资料效果一览 基本描述 传染病学模型 | Matlab实现SIR传染病学模型 (SIR Epidemic Model) 模型介绍 SIR模型是一种基本的传染病学模型,用于描述一个人群中某种传染病的传播情况。SIR模型假设每个人可以被感染,感染后会进入恢复…

Kibana:创建你的第一个仪表板

了解从你自己的数据创建仪表板的最常用方法。 本教程将从分析师查看网站日志的角度使用示例数据&#xff0c;但这种类型的仪表板适用于任何类型的数据。 完成后&#xff0c;你将全面了解示例 Web 日志数据。 在本次的展示中&#xff0c;我将使用最新的 Elastic Stack 8.7.1 来…

百分比图:解读数据,驱动业务增长

在当今信息爆炸的时代&#xff0c;数据成为了各行各业决策的重要依据。而在数据展示的众多形式中&#xff0c;百分比图凭借其简洁直观的表达方式和强大的信息传递能力&#xff0c;成为了企业和组织不可或缺的工具。本文将带您一同探索百分比图的魅力&#xff0c;揭示其在决策智…

介绍一下全链路压测平台的相关内容

随着互联网技术的不断发展&#xff0c;越来越多的企业开始依赖互联网来实现业务的发展和增长。而对于这些企业而言&#xff0c;如何保证他们的业务在高并发、高负载的情况下依然能够正常运行&#xff0c;是非常重要的一个问题。为了解决这个问题&#xff0c;企业可以使用全链路…

Vue3+ElementPlus报错集锦

目录 1、导入TS类型报错 2、使用类型报错 3、Vue3引入文件爆红且不提示 4、为defineAsyncComponent引入的component子组件设置类型 1、导入TS类型报错 &#xff08;1&#xff09;报错信息 import type { FormInstance, FormRules } from element-plus 模块 ""e…

精彩回顾 | Fortinet Accelerate 2023·中国区巡展杭州站

Fortinet Accelerate 2023中国区巡展 5月18日&#xff0c;Fortinet Accelerate 2023中国区巡展来到杭州&#xff0c;Fortinet携手太平洋电信、亚马逊云科技等云、网、安合作伙伴&#xff0c;与各行业典型代表客户&#xff0c;就网安融合、网安协同、工业互联网安全、云安全、网…

LC-1080. 根到叶路径上的不足节点(递归DFS)

1080. 根到叶路径上的不足节点 难度中等126 给你二叉树的根节点 root 和一个整数 limit &#xff0c;请你同时删除树中所有 不足节点 &#xff0c;并返回最终二叉树的根节点。 假如通过节点 node 的每种可能的 “根-叶” 路径上值的总和全都小于给定的 limit&#xff0c;则该…

网络安全有什么学习误区?

一、网络安全学习的误区 1.不要试图以编程为基础去学习网络安全 不要以编程为基础再开始学习网络安全&#xff0c;一般来说&#xff0c;学习编程不但学习周期长&#xff0c;且过渡到网络安全用到编程的用到的编程的关键点不多。一般人如果想要把编程学好再开始学习网络安全往…

【STM32系列】基础操作及LED测试

【STM32系列】基础操作及LED测试 资源常用网站整理基本操作恢复出厂设置 欢迎收看由咸鱼菌工作室出品的STM32系列教程。本篇内容主要是开发板的基础操作 资源 首先给大家推荐一些学习micropython的资源网站&#xff0c;文字版直接去我的博客里面翻一下 以下是一些Micropyth…

redis问题汇总

redis的优点 读写性能优异。十万/s的量级&#xff1b; 支持数据持久化。AOF,RDB 支持丰富的数据类型&#xff1b; 支持集群&#xff0c;可以实现主从复制&#xff0c;哨兵机制迁移&#xff0c;扩容等 缺点&#xff1a; 因为是基于内存的&#xff0c;所以虽然redis本身有key过期…

单片机如何通过PWM脉冲控制电机转速?

通过单片机实现对电机自动化控制已经在各行各业得到广泛应用&#xff0c;电机转速灵活使用方便&#xff0c;控制性能好&#xff0c;易于大范围调速。单片机通过PWM脉冲控制电机转速&#xff0c;在现代化生产中起到重要作用。 单片机是一种集成电路芯片&#xff0c;包括处理器、…

传染病学模型 | Matlab实现SIS传染病学模型 (SIS Epidemic Model)

文章目录 效果一览基本描述模型介绍程序设计参考资料效果一览 基本描述 传染病学模型 | Matlab实现SIS传染病学模型 (SIS Epidemic Model) 模型介绍 SIS模型是一种基本的传染病学模型,用于描述一个人群中某种传染病的传播情况。SIS模型假设每个人都可以被感染,即没有免疫力,…

PHP中常见的错误与异常处理总结大全

前言 当我们开发程序时&#xff0c;程序出现问题是很常见的&#xff0c;当出现了异常与错误我们该如何处理呢&#xff1f;本文将详细给大家介绍PHP错误与异常处理的相关内容&#xff0c;分享出来供大家参考学习&#xff0c;下面话不多说了&#xff0c;来一起看看详细的介绍&am…

【发电厂用JDHF-1010 合闸(分闸)监测继电器(220V/110V) JOSEF约瑟】

■JDHF-1000合闸(分闸)监测继电器主要用于各种保护和自动控制装置中&#xff0c;作为断路器操作运行状态的监测继电器。■交直流两用■监测继电器具有高内阻特性&#xff0c;可适应各种框架式断路器的合分回路。■快速导轨安装结构&#xff0c;适合各种导轨安装。■螺钉压接式端…

软件详细设计总复习(三)【太原理工大学】

题型及分值&#xff1a; 选择 30 分&#xff0c;填空 20 分&#xff0c; 判断 10 分&#xff0c;简答 20 分&#xff0c;综合设计 20 分。 文章目录 三、行为型模式1. 命令模式2. 迭代器模式3. 观察者模式4. 状态模式5. 策略模式 三、行为型模式 1. 命令模式 举个例子&#x…

面试踩坑合集

文章目录 前言一、String1、String的常用方法 二、多线程1、有几种线程池 三、集合1、hashmap和hashtable的区别2、红黑树转链表的条件 四、SpringMvc1、springMVC的处理流程 五、Sql1、把班级看做一张表&#xff0c;男女平均年纪和人数总数&#xff0c;根据性别分组2、Mysql事…

Kelvin和Rossby波 Part-1(简要介绍)

Equatorial Kelvin and Rossby Waves 赤道Kelvin和Rossby波&#xff1b;在该部分简要介绍 Kelvin waves和Rossby waves是海洋对西风突发等外界作用力变化的调整方式。这种调整是通过受重力、科氏力f以及科氏力的南北变化 ∂ f / ∂ y β \partial f/\partial yβ ∂f/∂yβ影响…

品优购项目06课后作业--产品详情页,text-align:justify属性无效,

文章目录 0.编程中出现的问题0.1 html文字字数不一致的布局方法&#xff0c;也就是如何实现文字俩端对齐&#xff1f;0.2 text-align:justify;属性无效怎么办&#xff1f; 1.结构分析1.1 快捷导航栏header头部模块nav导航模块1.2 主产品模块1.3 产品详情模块 2.代码部分2.1 主产…

消息中间件——RocketMQ(与Kafka、RabbitMQ的对比)

RocketMQ、Kafka、RabbitMQ的对比 1.ActiveMQ:Apache出品的比较老的消息中间件 2.Kafka:支持日志消息,监控数据,是一种高吞吐量的分布式发布订阅消息系统,支持百万级别的单机吞吐量,但是可能会造成数据丢失 3.RocketMQ:阿里在使用Kafka之后发现了它的消息系统主要定位于日志传…