本文已收录到 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. java基础--lambda表达式

    lambda表达式,一种常见用法,就是简化匿名内部类.使用前提条件:如果一个方法A(),只涉及一个抽象方法待实现,那么使用A()时,涉及到匿名内部类,就可以简化为 lambda 表达式 lambda表 ...

  2. 面试某大厂,被Channel给吊打了,这次一次性通关channel!

    目录 一 前言 面试题 然后我们进行一下扩展,玩转Channel! 二 解决面试题 1. 介绍一下Channel 2. Channel在go中起什么作用 3. Channel为什么需要两个队列实现 4 ...

  3. 25-tree shaking(树摇)

    const { resolve } = require('path'); const MiniCssExtractPlugin = require('mini-css-extract-plugin') ...

  4. vs 解决方案定位当前打开的cs文件

    可以通过工具-选项-项目和解决方案-勾选[在解决方案资源管理器中跟踪活动项]

  5. 【LeetCode动态规划#12】详解买卖股票I~IV,经典dp题型

    买卖股票的最佳时机 力扣题目链接(opens new window) 给定一个数组 prices ,它的第 i 个元素 prices[i] 表示一支给定股票第 i 天的价格. 你只能选择 某一天 买入 ...

  6. Netty介绍与认识

    概述 Netty是由JBOSS提供的一个java开源框架.Netty提供异步的.事件驱动的网络应用程序框架和工具,用以快速开发高性能.高可靠性的网络服务器和客户端程序. 2.体系结构图 Netty的核 ...

  7. Django框架简单搭建增删改查页面 Django请求生命周期流程图

    目录 Django框架简单搭建增删改查页面 一.前期的配置文件以及连接MySQL的基本准备 二.在数据库中准备好数据 三.将MySQL的数据展示到页面(简单认识HTML模板语法 for循环) 在Dja ...

  8. Jmeter-测试报告模板分享

    1.jmeter-results-detail-report_21 <?xml version="1.0"?> <!-- ~ Licensed to the Ap ...

  9. Langchain框架 prompt injection注入

    Langchain框架 prompt injection注入 Prompt Injection 是一种攻击技术,黑客或恶意攻击者操纵 AI 模型的输入值,以诱导模型返回非预期的结果 Langchain ...

  10. 【python】使用爬虫爬取动漫之家漫画全部更新信息

    本篇仅在于交流学习 网站名称为: https://manhua.dmzj.com/ 1.首先将相应的库导入: import requests from lxml import etree 2.确定漫画 ...