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

单周赛 345 概览

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

标签:栈

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

标签:贪心、双指针

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

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

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

标签:贪心、最短路


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

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

题解(栈)

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

  1. class Solution {
  2. fun minLength(s: String): Int {
  3. val stack = ArrayDeque<Char>()
  4. for (c in s) {
  5. if (c == 'D' && stack.isNotEmpty() && stack.peek() == 'C') stack.pop()
  6. else if (c == 'B' && stack.isNotEmpty() && stack.peek() == 'A') stack.pop()
  7. else stack.push(c)
  8. }
  9. return stack.size
  10. }
  11. }

复杂度分析:

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

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

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

题解(贪心)

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

  1. class Solution {
  2. fun makeSmallestPalindrome(s: String): String {
  3. val arr = s.toCharArray()
  4. val n = s.length
  5. // 判断回文串写法
  6. for (i in 0 until n / 2) {
  7. val j = n - 1 - i
  8. if(arr[i] != arr[j]) {
  9. val temp = if(arr[i] < arr[j]) arr[i] else arr[j]
  10. arr[i] = temp
  11. arr[j] = temp
  12. }
  13. }
  14. return String(arr)
  15. }
  16. }

复杂度分析:

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

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

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

题解一(子集型回溯)

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

  1. class Solution {
  2. fun punishmentNumber(n: Int): Int {
  3. if (n <= 3) return 1
  4. var ret = 0
  5. for (x in 4 .. n) {
  6. val target = x * x
  7. if (backTrack("$target", 0, x)) ret += target
  8. }
  9. return ret + 1 /* 1 满足条件 */
  10. }
  11. // 子集型回溯
  12. private fun backTrack(str : String, i : Int, target : Int) : Boolean {
  13. if (i == str.length) return target == 0
  14. var cur = 0
  15. for (to in i until str.length) {
  16. cur = cur * 10 + (str[to] - '0')
  17. if (backTrack(str, to + 1, target - cur)) return true
  18. }
  19. return false
  20. }
  21. }

复杂度分析:

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

题解二(状态压缩)

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

  1. class Solution {
  2. fun punishmentNumber(n: Int): Int {
  3. if (n <= 3) return 1
  4. var ret = 0
  5. for (x in 4 .. n) {
  6. val target = x * x
  7. if (check("$target", x)) ret += target
  8. }
  9. return ret + 1 /* 1 满足条件 */
  10. }
  11. // 状态压缩
  12. private fun check(str : String, target : Int) : Boolean {
  13. val m = str.length
  14. val upper = (1 shl m) - 1
  15. for (k in 1 .. upper) {
  16. var last = 0
  17. var sum = 0
  18. for (i in 0 until m) {
  19. val cur = str[i] - '0'
  20. if (k and (1 shl i) != 0) {
  21. // 拆
  22. sum += last
  23. last = cur
  24. } else{
  25. // 不拆
  26. last = last * 10 + cur
  27. }
  28. }
  29. if (sum + last == target) return true
  30. }
  31. return false
  32. }
  33. }

复杂度分析:

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

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

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

  1. class Solution {
  2. companion object {
  3. private val U = 1000
  4. private val preSum = IntArray(U + 1)
  5. init {
  6. for (x in 4 .. U) {
  7. val target = x * x
  8. if (check("$target", x)) preSum[x] += target
  9. preSum[x] += preSum[x - 1]
  10. }
  11. }
  12. // 状态压缩
  13. private fun check(str : String, target : Int) : Boolean {
  14. }
  15. }
  16. fun punishmentNumber(n: Int): Int {
  17. return preSum[n] + 1
  18. }
  19. }

复杂度分析:

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

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

  1. 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。
  1. class Solution {
  2. private val INF = 1e9.toInt()
  3. fun modifiedGraphEdges(n: Int, edges: Array<IntArray>, source: Int, destination: Int, target: Int): Array<IntArray> {
  4. if (source !in 0 .. n - 1 || destination !in 0 .. n - 1) return edges
  5. if (source == destination || edges.isNullOrEmpty()) return edges
  6. // 建图(领接表,节点号 + 边号方便修改边权)
  7. val graph = Array(n) { ArrayList<IntArray>() }
  8. for ((i, edge) in edges.withIndex()) {
  9. graph[edge[0]].add(intArrayOf(edge[1], i))
  10. graph[edge[1]].add(intArrayOf(edge[0], i))
  11. }
  12. // 第一轮最短路
  13. val originDis = dijkstra1(graph, edges, source, destination)
  14. if (originDis[destination] > target) return emptyArray() // 无解
  15. // 第二轮最短路
  16. val delta = target - originDis[destination] // 需要补全的最短路
  17. val dis = dijkstra2(graph, edges, source, destination, delta, originDis)
  18. if (dis[destination] < target) return emptyArray() // 无解
  19. // 修改剩余边
  20. for (edge in edges) {
  21. if (edge[2] == -1) edge[2] = INF
  22. }
  23. return edges
  24. }
  25. // return:将 -1 视为 1,并计算从起点到终点的最短路
  26. private fun dijkstra1(graph:Array<ArrayList<IntArray>>, edges: Array<IntArray>, source :Int, destination:Int) : IntArray {
  27. val n = graph.size
  28. val visit = BooleanArray(n)
  29. val dis = IntArray(n) { INF }
  30. dis[source] = 0
  31. while (true) {
  32. // 寻找最短路长度最短的节点
  33. var x = -1
  34. for (i in 0 until n) {
  35. if (visit[i]) continue
  36. if (-1 == x || dis[i] < dis[x]) x = i
  37. }
  38. if (x == destination) break
  39. visit[x] = true // 标记
  40. // 松弛相邻边
  41. for (to in graph[x]) {
  42. var w = edges[to[1]][2]
  43. if (-1 == w) w = 1 // 视为 1
  44. if (dis[x] + w < dis[to[0]]) dis[to[0]] = dis[x] + w
  45. }
  46. }
  47. return dis
  48. }
  49. // 补全
  50. private fun dijkstra2(graph:Array<ArrayList<IntArray>>, edges: Array<IntArray>, source :Int, destination:Int, delta: Int, originDis:IntArray /* 首轮计算的最短路 */) : IntArray {
  51. val n = graph.size
  52. val visit = BooleanArray(n)
  53. val dis = IntArray(n) { INF }
  54. dis[source] = 0
  55. while (true) {
  56. // 寻找最短路长度最短的节点
  57. var x = -1
  58. for (i in 0 until n) {
  59. if (visit[i]) continue
  60. if (-1 == x || dis[i] < dis[x]) x = i
  61. }
  62. if (x == destination) break
  63. visit[x] = true // 标记
  64. // 松弛相邻边
  65. for (to in graph[x]) {
  66. var w = edges[to[1]][2]
  67. if (-1 == w) {
  68. // 补全(两次 Dijkstra 只修改这里)
  69. w = Math.max(delta - dis[x] + originDis[to[0]], 1) // 题目要求至少修改到 1
  70. if (w >= 1) edges[to[1]][2] = w
  71. }
  72. if (dis[x] + w < dis[to[0]]) dis[to[0]] = dis[x] + w
  73. }
  74. }
  75. return dis
  76. }
  77. }

复杂度分析:

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

往期回顾

LeetCode 周赛 346(2023/05/21)仅 68 人 AK 的最短路问题的更多相关文章

  1. windows server 2008 R2域中的DC部署 分类: AD域 Windows服务 2015-06-06 21:09 68人阅读 评论(0) 收藏

    整个晚上脑子都有点呆滞,想起申请注册好的博客还从来都不曾打理,上来添添生机.从哪里讲起呢,去年有那么一段时间整个人就陷在域里拔不出来,于是整理了一些文档,害怕自己糊里糊涂的脑子将这些东西会在一觉醒来全 ...

  2. UI基础:UITextField 分类: iOS学习-UI 2015-07-01 21:07 68人阅读 评论(0) 收藏

    UITextField 继承自UIControl,他是在UILabel基础上,对了文本的编辑.可以允许用户输入和编辑文本 UITextField的使用步骤 1.创建控件 UITextField *te ...

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

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

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

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

  5. Microsoft Artificial Intelligence Conference(2018.05.21)

    时间:2018.05.21地点:北京嘉丽大酒店

  6. 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 ...

  7. LeetCode 周赛 334,在算法的世界里反复横跳

    本文已收录到 AndroidFamily,技术和职场问题,请关注公众号 [彭旭锐] 提问. 大家好,我是小彭. 今天是 LeetCode 第 334 场周赛,你参加了吗?这场周赛考察范围比较基础,整体 ...

  8. LeetCode 周赛 338,贪心 / 埃氏筛 / 欧氏线性筛 / 前缀和 / 二分查找 / 拓扑排序

    本文已收录到 AndroidFamily,技术和职场问题,请关注公众号 [彭旭锐] 提问. 大家好,我是小彭. 上周末是 LeetCode 第 338 场周赛,你参加了吗?这场周赛覆盖的知识点很多,第 ...

  9. 刷爆 LeetCode 周赛 339,贪心 / 排序 / 拓扑排序 / 平衡二叉树

    本文已收录到 AndroidFamily,技术和职场问题,请关注公众号 [彭旭锐] 提问. 大家好,我是小彭. 上周末是 LeetCode 第 339 场周赛,你参加了吗?这场周赛覆盖的知识点比较少, ...

  10. LeetCode 周赛 340,质数 / 前缀和 / 极大化最小值 / 最短路 / 平衡二叉树

    本文已收录到 AndroidFamily,技术和职场问题,请关注公众号 [彭旭锐] 提问. 大家好,我是小彭. 上周跟大家讲到小彭文章风格的问题,和一些朋友聊过以后,至少在算法题解方面确定了小彭的风格 ...

随机推荐

  1. Trino Master OOM 排查记录

    背景 最近线上的 trino 集群 master 节点老是因为 OOM crash,我们注意到 trino crash 前集群正在运行的查询数量正常,不太像是因为并发查询数据太多导致的 OOM.遂配置 ...

  2. 声网 Agora 音频互动 MoS 分方法:为音频互动体验进行实时打分

    在业界,实时音视频的 QoE(Quality of Experience) 方法一直都是个重要的话题,每年 RTE 实时互联网大会都会有议题涉及.之所以这么重要,其实是因为目前 RTE 行业中还没有一 ...

  3. MGF multivariate generating function 多变量生成函数

    目录 MGF多变量生成函数multivariate generating function 定义 例子 Extremal parameters III.8.1 largest components 例 ...

  4. 疯一样的向自己发问 - 剖析lsm 索引原理

    疯一样的向自己发问 - 剖析lsm 索引原理 lsm简析 lsm 更像是一种设计索引的思想.它把数据分为两个部分,一部分放在内存里,一部分是存放在磁盘上,内存里面的数据检索方式可以利用红黑树,跳表这种 ...

  5. [架构]辨析: 高可用 | 集群 | 主从 | 负载均衡 | 反向代理 | 中间件 | 微服务 | 容器 | 云原生 | DevOps | ...

    词汇集 灾备 冷备份 双机热备份 异地容灾备份 云备份 灾难演练 磁盘阵列(RAID) 故障切换 心跳监测 高可用 集群 主从复制(Master-Slave) 多集群横向扩容(master-clust ...

  6. JUC(七)分支合并框架

    JUC分支合并框架 简介 Fork/Join可以将一个大的任务拆分成多个子任务进行并行处理,最后将子任务的结果合并称为最终的计算结果. Fork:负责将任务拆分 Join:合并拆分任务 ForkJoi ...

  7. 学习C语言的第一天

    今天学习C语言学习了三个部分: 第一个部分是软件环境的搭建,如何搭建一个项目 使用工具:visual studio 2010 搭建过程:新建项目.配置设置(主要是解决运行后一闪而过的问题) 第二部分是 ...

  8. extend笔记

    JavaScript面向对象 继承extend 1. 概念(主要用途) 将子类中的共性代码 ( 属性和方法 ) 抽取出来 放到父类中 每当有一个新的子类需要用到共性的属性或者方法时 不需要在自己内容复 ...

  9. python工程师-day83

    1.drf 的用户认证组件 (1)models from django.db import models# Create your models here.class User(models.Mode ...

  10. chatgpt接口开发笔记1:completions接口

    chatgpt接口开发笔记1:completions接口 个人博客地址: https://note.raokun.top 拥抱ChatGPT,国内访问网站:https://www.playchat.t ...