本文已收录到 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. Kubernetes客户端认证(二)—— 基于ServiceAccount的JWTToken认证

    1.概述 在 Kubernetes 官方手册中给出了 "用户" 的概念,Kubernetes 集群中存在的用户包括 "普通用户" 与 "Service ...

  2. day60:Linux压缩与打包&用户管理&用户提权sudo&grep,sed,awk,sort,uniq

    目录 1.文件管理-压缩与打包 2.用户管理 用户怎么查 如何创建用户 创建的用户信息都存储在哪? 用户存储密码的文件 如何为用户设定密码? 3.用户组 4.用户提权相关 5.Extra:额外补充 文 ...

  3. django渲染模版时比实际少了8小时?

    这是因为django的时间是UTC时间. 我们通过改配置文件将其改成本地时间 修改配置文件 # 将时间从UTC转化成当前时间 TIME_ZONE = 'Asia/Shanghai' # USE_TZ ...

  4. canvas-绘制3D金字塔

    var canvas1 = document.getElementById("canvas1"); var context = canvas1.getContext("2 ...

  5. 关于Java中代码的执行顺序

    结论 注意 只有显式的加载类 JVM才会加载到内存中 先加载父类的静态代码块 然后执行子类静态代码块 当前类存在类静态变量注意引用类型没进行赋值操作初始化为null 并不会显式的加载类又存在静态代码块 ...

  6. MAPPO学习笔记(1):从PPO算法开始

    由于这段时间的学习内容涉及到MAPPO算法,并且我对MAPPO算法这种多智能体算法的信息交互机制不甚了解,于是写了这个系列的笔记,目的是巩固知识,并且进行一些粗浅又滑稽的总结. 1.PPO算法的介绍 ...

  7. macOS下安装 n 管理包(node版本管理工具)

    1. 安装 n 管理包 终端命令全局安装 npm install -g n 安装成功后在终端输入 n --version 或 n 查看,可看到 n 的默认安装目录 下面就是使用 n 的方式了, 首先查 ...

  8. 面试题:react、vue中的key

    1.虚拟DOM中key的作用     key是虚拟DOM对象的标识,当数据发生变化时,React/Vue会根据[新数据]生成新的[虚拟DOM],随后React/Vue进行[新虚拟DOM]与[旧虚拟DO ...

  9. 在Bamboo上怎么使用iOS的单元测试

    作者:京东零售 吴滔 本教程将使用北汽登录模块为例,一步一步和大家一起搭建单元测试用例,并在Bamboo上跑起来,最终测试结果和代码覆盖率会Bamboo上汇总. 模块名称:BQLoginModule, ...

  10. S5PV210 | 裸机汇编LED流水灯实验

    S5PV210 | 裸机汇编LED流水灯实验 目录 S5PV210 | 裸机汇编LED流水灯实验 开发板: 1.原理图 2.Datasheet相关 3.代码 3-1.代码实现(流水灯,仅作演示) 3- ...