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

双周赛 108 概览

T1. 最长交替子序列(Easy)

  • 标签:模拟、同向双指针

T2. 重新放置石块(Medium)

  • 标签:模拟、散列表

T3. 将字符串分割为最少的美丽子字符串(Medium)

  • 标签:记忆化递归、动态规划

T4. 黑格子的数目(Medium)

  • 标签:枚举、贡献


T1. 最长交替子序列(Easy)

https://leetcode.cn/problems/longest-alternating-subarray/

题解一(模拟)

这道题与上周周赛 T1 还是比较相似的。

使用两层循环,枚举从每个元素 nums[i] 为起点开始的最长交替子序列长度。

class Solution {
fun alternatingSubarray(nums: IntArray): Int {
var ret = -1
for (i in 0 until nums.size) {
var target = 1
for (j in i + 1 until nums.size) {
if (nums[j] - nums[j - 1] != target) break
ret = Math.max(ret, j - i + 1)
target *= -1
}
}
return ret
}
}

复杂度分析:

  • 时间复杂度:$O(n^2)$ 其中 n 为 nums 数组的长度;
  • 空间复杂度:仅使用常量级别空间。

题解二(同向双指针)

这个解法基于 KMP 思想。

在题解一中,我们会重复计算同一段交替子序列的,我们可以使用一次遍历,再交替子序列终止时避免重复回退到该子序列内部。需要注意的是,由于不同的交替子序列可能存在 1 位重叠,所以要把 i 指针指向 j 指针,而不是指向 j 指针的下一位,才能保证没有缺失。例如 [3,4,3,4,5,4,5] 数组,第一组交替子数组为 [3,4,3,4] 和第二组交替子数组为 [4,5,4,5] 这两组有重叠部分。

class Solution {
fun alternatingSubarray(nums: IntArray): Int {
val n = nums.size
var ret = -1
var i = 0
while (i < n - 1) {
// 寻找起点
while (i < n - 1 && nums[i + 1] - nums[i] != 1) {
i++
}
var target = 1
var j = i
while (j < n - 1 && nums[j + 1] - nums[j] == target) {
ret = Math.max(ret, ++j - i + 1)
target *= -1
}
i = j
}
return ret
}
}

复杂度分析:

  • 时间复杂度:$O(n)$ 线性遍历
  • 空间复杂度:$O(1)$ 仅使用常量级别空间。

T2. 重新放置石块(Medium)

https://leetcode.cn/problems/relocate-marbles/

题解(模拟 + 散列表)

在每部操作中,我们会将位置 moveFrom[i] 上所有的石头移动到 moveTo[i] 上,「所有」的含义意味着石头的数量是无关紧要的,我们可以使用散列表维护剩余的石头,最后对剩余石头排序。

class Solution {
fun relocateMarbles(nums: IntArray, moveFrom: IntArray, moveTo: IntArray): List<Int> {
if (moveFrom.size != moveTo.size) return Collections.emptyList()
val set = nums.toHashSet()
for (i in moveFrom.indices) {
set.remove(moveFrom[i])
set.add(moveTo[i])
}
return set.toMutableList().sorted()
}
}

复杂度分析:

  • 时间复杂度:$O(nlgn)$ 瓶颈在排序上;
  • 空间复杂度:$O(n)$ 散列表空间。

T3. 将字符串分割为最少的美丽子字符串(Medium)

https://leetcode.cn/problems/partition-string-into-minimum-beautiful-substrings/

题解一(记忆化递归)

比较直观的子集问题,我们枚举所有分割点(可以构造 5 的幂)的位置并记录最短结果。由于题目的数据范围比较小,我们可以预处理出数据范围内所有 5 的幂。

  • 定义 backTrack(i) 表示从 [i] 为起点的最少美丽字符串个数,枚举以 [i] 为起点的所有可行方案,从中得出最优解。
class Solution {

    companion object {
// 预处理
private val U = 15
private val INF = Integer.MAX_VALUE
private val set = HashSet<Int>()
init {
var x = 1
while (x.toString(2).length <= U) {
set.add(x)
x *= 5
}
}
} fun minimumBeautifulSubstrings(s: String): Int {
return backTrack(s, HashMap<Int,Int>(), 0)
} private fun backTrack(s: String, memo: MutableMap<Int, Int>, i: Int): Int {
// 终止条件
if (i == s.length) return 0
// 剪枝(不允许前导零)
if (s[i] == '0') return -1
// 读备忘录
if (memo.contains(i)) return memo[i]!!
// 枚举
var x = 0
var ret = INF
for (j in i until s.length) {
x = x.shl(1) + (s[j] - '0')
if (set.contains(x)) {
// 递归
val childRet = backTrack(s, memo, j + 1)
if (-1 != childRet) ret = Math.min(ret, childRet)
}
}
val finalRet = if (INF == ret) -1 else ret + 1
memo[i] = finalRet
return finalRet
}
}

复杂度分析:

  • 时间复杂度:$O(n^2)$ 一共 n 个分割点,每个分割点有「选和不选」两种方案,看起来总共有 $2^n$ 种子状态,其实并没有。我们的 backTrack(i) 的定义是以 [i] 为起点可以构造的最少美丽字符串数,因此总共只有 n 种状态,而每种状态需要检查 $O(n)$ 种子状态,因此整体时间复杂度是 $O(n^2)$;
  • 空间复杂度:$O(n)$ 备忘录空间。

题解二(动态规划)

可以把记忆化递归翻译为动态规划的版本:

class Solution {

    companion object {
// 预处理
private val U = 15
private val INF = Integer.MAX_VALUE
private val set = HashSet<Int>()
init {
var x = 1
while (x.toString(2).length <= U) {
set.add(x)
x *= 5
}
}
} fun minimumBeautifulSubstrings(s: String): Int {
val INF = 0x3F3F3F3F // 便于判断
val n = s.length
val dp = IntArray(n + 1) { INF }
dp[n] = 0
// 倒序遍历(先求小问题)
for (i in n - 1 downTo 0) {
// 不允许前导零
if (s[i] == '0') continue
// 枚举
var x = 0
for (j in i until n) {
x = x.shl(1) + (s[j] - '0')
if (set.contains(x)) dp[i] = Math.min(dp[i], dp[j + 1] + 1)
}
}
return if (dp[0] != INF) dp[0] else -1
}
}

复杂度分析:

  • 时间复杂度:$O(n^2)$ 同上;
  • 空间复杂度:$O(n)$ DP 数组空间。

T4. 黑格子的数目(Medium)

https://leetcode.cn/problems/number-of-black-blocks/

题解(枚举黑格 + 贡献度)

直接枚举所有块的时间复杂度是 O(nm) 会超时,我们发现真正影响结果的是黑格格子,但是暴力枚举块的方法会枚举到那些完全是白色的块。

因此,我们将枚举维度从所有块调整到黑色格子附近的块,对于每一个黑色格子 [x, y] 最多仅会对 4 个块产生影响(贡献)。所以我们的算法是:枚举所有黑色格子,并记录黑色格子可以产生贡献的块,最后统计出所有可以被影响到的块以及的贡献度,这可以用散列表来记录。

剩下一个问题是怎么表示一个唯一的块,我们可以规定块中 4 个点中的其中一个点作为块的代表元(以右下角的点为例),然后将该点的行和列压缩到一个 Long 变量中来唯一标识不同的块。


class Solution {
fun countBlackBlocks(m: Int, n: Int, coordinates: Array<IntArray>): LongArray {
val U = 100000
val map = HashMap<Long, Int>()
// 以右下角为代表元的块
val blocks = arrayOf(intArrayOf(0,0), intArrayOf(0, 1), intArrayOf(1,1), intArrayOf(1,0))
for (e in coordinates) {
// 枚举 4 个块
for (block in blocks) {
val x = e[0] + block[0]
val y = e[1] + block[1]
// 检查块有效性
if (x >= 1 && x < m && y >= 1 && y < n) {
// 记录贡献度
val key = 1L * x * U + y
map[key] = map.getOrDefault(key, 0) + 1
}
}
}
val ret = LongArray(5)
for ((_, cnt) in map) {
ret[cnt] ++
}
ret[0] = 1L * (n - 1) * (m - 1) - map.size
return ret
}
}

复杂度分析:

  • 时间复杂度:$O(m)$ 其中 m 为黑格格子数
  • 空间复杂度:$O(m)$ 其中 m 为黑格格子数

往期回顾

LeetCode 周赛(2023/07/08)渐入佳境的更多相关文章

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

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

  2. http://www.blogjava.net/xylz/archive/2010/07/08/325587.html

    http://www.blogjava.net/xylz/archive/2010/07/08/325587.html

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

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

  4. 【Leetcode周赛】从contest-111开始。(一般是10个contest写一篇文章)

    Contest 111 (题号941-944)(2019年1月19日,补充题解,主要是943题) 链接:https://leetcode.com/contest/weekly-contest-111 ...

  5. LeetCode 周赛 332,在套路里摸爬滚打~

    本文已收录到 AndroidFamily,技术和职场问题,请关注公众号 [彭旭锐] 提问. 大家好,今天是 3T 选手小彭. 上周是 LeetCode 第 332 场周赛,你参加了吗?算法解题思维需要 ...

  6. LeetCode 周赛 333,你管这叫 Medium 难度?

    本文已收录到 AndroidFamily,技术和职场问题,请关注公众号 [彭旭锐] 提问. 大家好,我是小彭. 上周是 LeetCode 第 333 场周赛,你参加了吗?这场周赛质量很高,但难度标得不 ...

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

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

  8. LeetCode 周赛 336,多少人直接 CV?

    本文已收录到 AndroidFamily,技术和职场问题,请关注公众号 [彭旭锐] 提问. 大家好,我是小彭. 今天早上是 LeetCode 第 336 场周赛,你参加了吗?这场周赛整体质量比较高,但 ...

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

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

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

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

随机推荐

  1. mysql迁移:mysqldump导出表结构及数据

    问题描述:有需要mysql某几张表的需求,某个数据库某几张表,导出先检查相应的数据库和表是否存在 数据泵用法:默认导出的是表结构以及表中的数据 mysqldump -uroot -p -S /data ...

  2. JUC(一)JUC简介与Synchronized和Lock

    1 JUC简介 JUC就是java.util.concurrent的简称,这是一个处理线程的工具包,JDK1.5开始出现的. 进程和线程.管程 进程:系统资源分配的基本单位:它是程序的一次动态执行过程 ...

  3. 网络框架重构之路plain2.0(c++23 without module) 环境

    接下来本来就直接打算分享框架重构的具体环节,但重构的代码其实并没有完成太多,许多的实现细节在我心中还没有形成一个定型.由于最近回归岗位后,新的开发环境需要自己搭建,搭建的时间来说花了我整整一天的时间才 ...

  4. Java 新的生态型应用开发框架,Solon v2.2.13 发布

    Java 新的生态型应用开发框架,Solon :更快.更小.更简单.从零开始构建,有自己的标准规范与开放生态: 150多个生态插件,可以满足各种场景开发 大量的国产框架适配,可以为应用软件国产化提供更 ...

  5. shell自动化脚本,启动、停止应用程序

    #!/usr/bin/env bash # 常量初始化 set_runtime_vars(){ # 日期时间 Now_Date=`date +"%Y-%m-%d %H:%M:%S" ...

  6. Prompt learning 教学[进阶篇]:简介Prompt框架并给出自然语言处理技术:Few-Shot Prompting、Self-Consistency等;项目实战搭建知识库内容机器人

    Prompt learning 教学[进阶篇]:简介Prompt框架并给出自然语言处理技术:Few-Shot Prompting.Self-Consistency等:项目实战搭建知识库内容机器人 1. ...

  7. 2021-02-06:假设字符串str长度为N,请问最长回文子串的长度是多少?

    福哥答案2021-02-06: 1.动态规划.无代码,见图.2.中心扩展法.无代码.3.Manacher算法.有代码,见图.1)理解回文半径数组.2)理解所有中心的回文最右边界R,和取得R时的中心点C ...

  8. 顶会ICSE-2023发布LIBRO技术,利用大模型技术进行缺陷重现,自动重现率达33%

    摘要:本文围绕LIBRO技术的主要步骤进行介绍. 本文分享自华为云社区<[LLM for SE]顶会ICSE-2023发布LIBRO技术,利用大模型技术进行缺陷重现,自动重现率(33%)实现业界 ...

  9. C端用户体验度量实战篇-京东快递小程序体验度量全面升级

    本文通过介绍体验度量模型升级研究过程.研究方法及研究结果等内容,结合实际C端产品应用,观测新模型运行周期的表现,验证了其在高速发展的业务形态和日益变化的用户需求上的适用性和有效性.我们从体验价值为导向 ...

  10. JDK动态代理和CGLIB代理有什么区别

    JDK动态代理和CGLIB代理都是实现Spring框架中AOP的代理方式,它们的实现原理和应用场景有所不同, 具体区别如下: 1. 实现原理: JDK动态代理是基于Java反射机制实现的,它要求目标类 ...