LeetCode 周赛 346(2023/05/21)仅 68 人 AK 的最短路问题
本文已收录到 AndroidFamily,技术和职场问题,请关注公众号 [彭旭锐] 提问。
单周赛 345 概览
T1. 删除子串后的字符串最小长度(Easy)
标签:栈
T2. 字典序最小回文串(Medium)
标签:贪心、双指针
T3. 求一个整数的惩罚数(Medium)
标签:回溯、状态压缩、前缀和
T4. 修改图中的边权(Hard)
标签:贪心、最短路

T1. 删除子串后的字符串最小长度(Easy)
https://leetcode.cn/problems/minimum-string-length-after-removing-substrings/
题解(栈)
使用栈模拟扫描过程,当扫描到 D 和 B 时检查栈顶元素,最后栈内剩余的元素个数就是无法消除的最小长度:
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)$ 其中 n 为 s 字符串的长度;
- 空间复杂度:$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)$ 其中 n 为 s 字符串的长度;
- 空间复杂度:$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)$ 每个数字 i 转字符串后的长度为 $log_i$,而枚举长度为 $log_i$ 的字符串的切分方案后 $2^{log_i}$ = i 种方案,因此整体的时间复杂度是 $O(n^2)$;
- 空间复杂度:$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)$ 仅使用常量级别空间。
题解三(预处理 + 前缀和)
题解一和题解二在多个测试用例间会重复计算相同数字的切分方案,我们可以预处理 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)$ 其中 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(m)$ 图空间。
往期回顾
- LeetCode 单周赛第 345 场 · 体验一题多解的算法之美
- LeetCode 单周赛第 344 场 · 手写递归函数的通用套路
- LeetCode 双周赛第 104 场 · 流水的动态规划,铁打的结构化思考
- LeetCode 双周赛第 103 场 · 区间求和的树状数组经典应用
LeetCode 周赛 346(2023/05/21)仅 68 人 AK 的最短路问题的更多相关文章
- windows server 2008 R2域中的DC部署 分类: AD域 Windows服务 2015-06-06 21:09 68人阅读 评论(0) 收藏
整个晚上脑子都有点呆滞,想起申请注册好的博客还从来都不曾打理,上来添添生机.从哪里讲起呢,去年有那么一段时间整个人就陷在域里拔不出来,于是整理了一些文档,害怕自己糊里糊涂的脑子将这些东西会在一觉醒来全 ...
- UI基础:UITextField 分类: iOS学习-UI 2015-07-01 21:07 68人阅读 评论(0) 收藏
UITextField 继承自UIControl,他是在UILabel基础上,对了文本的编辑.可以允许用户输入和编辑文本 UITextField的使用步骤 1.创建控件 UITextField *te ...
- LeetCode 周赛 342(2023/04/23)容斥原理、计数排序、滑动窗口、子数组 GCB
本文已收录到 AndroidFamily,技术和职场问题,请关注公众号 [彭旭锐] 提问. 大家好,我是小彭. 前天刚举办 2023 年力扣杯个人 SOLO 赛,昨天周赛就出了一场 Easy - Ea ...
- 刷爆 LeetCode 周赛 337,位掩码/回溯/同余/分桶/动态规划·打家劫舍/贪心
本文已收录到 AndroidFamily,技术和职场问题,请关注公众号 [彭旭锐] 提问. 大家好,我是小彭. 上周末是 LeetCode 第 337 场周赛,你参加了吗?这场周赛第三题有点放水,如果 ...
- Microsoft Artificial Intelligence Conference(2018.05.21)
时间:2018.05.21地点:北京嘉丽大酒店
- http://www.cnblogs.com/ITtangtang/archive/2012/05/21/2511749.html
http://www.cnblogs.com/ITtangtang/archive/2012/05/21/2511749.html http://blog.sina.com.cn/s/blog_538 ...
- LeetCode 周赛 334,在算法的世界里反复横跳
本文已收录到 AndroidFamily,技术和职场问题,请关注公众号 [彭旭锐] 提问. 大家好,我是小彭. 今天是 LeetCode 第 334 场周赛,你参加了吗?这场周赛考察范围比较基础,整体 ...
- LeetCode 周赛 338,贪心 / 埃氏筛 / 欧氏线性筛 / 前缀和 / 二分查找 / 拓扑排序
本文已收录到 AndroidFamily,技术和职场问题,请关注公众号 [彭旭锐] 提问. 大家好,我是小彭. 上周末是 LeetCode 第 338 场周赛,你参加了吗?这场周赛覆盖的知识点很多,第 ...
- 刷爆 LeetCode 周赛 339,贪心 / 排序 / 拓扑排序 / 平衡二叉树
本文已收录到 AndroidFamily,技术和职场问题,请关注公众号 [彭旭锐] 提问. 大家好,我是小彭. 上周末是 LeetCode 第 339 场周赛,你参加了吗?这场周赛覆盖的知识点比较少, ...
- LeetCode 周赛 340,质数 / 前缀和 / 极大化最小值 / 最短路 / 平衡二叉树
本文已收录到 AndroidFamily,技术和职场问题,请关注公众号 [彭旭锐] 提问. 大家好,我是小彭. 上周跟大家讲到小彭文章风格的问题,和一些朋友聊过以后,至少在算法题解方面确定了小彭的风格 ...
随机推荐
- Javaweb问题解决--在.java文件里面写sql语句模糊查询不成功的原因
问题描述 在上学期,我就遇到了这个问题(别骂别骂),然后当时卡住之后,Mybatis闯入了我的视线,然后直接换用了较为方便的Mybatis框架结构,这个问题也就被搁置了,今天重新提起,优势慢慢地查阅了 ...
- 保姆级本地maven安装配置步骤【Windows】
一.前期准备 1.首先需要安装并配置好本地JDK(WIN+R输入cmd,输入java -version如下图) 2.下载maven到本地(链接Maven – Download Apache Maven ...
- P8112 符文破译
题目描述 将字符串 \(T\) 拆成若干个子串,使这些子串为字符串 \(S\) 的前缀,要求拆分形成的子串数最小. 思路整理 实际上并不需要倒着枚举,也不需要线段树,更不需要 Z 函数. 如果你做过 ...
- ByteHouse:基于 ClickHouse 的实时计算能力升级
更多技术交流.求职机会,欢迎关注字节跳动数据平台微信公众号,回复[1]进入官方交流群 ByteHouse 是火山引擎数智平台旗下云原生数据分析平台,为用户带来极速分析体验,能够支撑实时数据分析和海量离 ...
- Yaml入门与使用
一.入门 1.概念: yml是YAML("YAML Ain't a Markup Language)语言文件,以数据为中心,而不是一标记语言为重点,比json,xml更适合做配置文件. 为 ...
- 仓库管理、dockerfile
Docker仓库管理 仓库(Repository)是集中存放镜像的地方. Docker Dockerfile 什么是Dockerfile? Dockerfile 是一个用来构建镜像的文本文件, ...
- vue中使用西瓜视频中引入自定义样式,绝对可以
首先配置sass-loader和raw-loader 方法,再vue-config.js中加上这一段代码 module.exports = { chainWebpack: config => { ...
- 管理WEB服务器文件的WebDAV协议&HTTP大跃进--QUIC与HTTP30&WEB安全攻击概述
管理WEB服务器文件的WebDAV协议 WebADV协议 WEBDAV追加方法 WeDAV请求示例 HTTP大跃进--QUIC与HTTP30 QUIC&HTTP3.0 HTTP2.0的问题 队 ...
- 安装 Metrics server
安装 Metrics server Metrics Server 是 Kubernetes 内置自动缩放管道的可扩展.高效的容器资源指标来源. Metrics Server 从 Kubelets 收集 ...
- CentOS 落幕,将于2021年底结束维护
官方最新消息: 译文: CentOS项目的未来是CentOS Stream,明年,我们将把重点从重建Red Hat Enterprise Linux(RHEL)的CentOS Linux转移到Cent ...