LeetCode 周赛上分之旅 # 36 KMP 字符串匹配殊途同归

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

学习数据结构与算法的关键在于掌握问题背后的算法思维框架,你的思考越抽象,它能覆盖的问题域就越广,理解难度也更复杂。在这个专栏里,小彭与你分享每场 LeetCode 周赛的解题报告,一起体会上分之旅。

本文是 LeetCode 上分之旅系列的第 36 篇文章,往期回顾请移步到文章末尾~

周赛 356

T1. 满足目标工作时长的员工数目

  • 标签:模拟

T2. 统计完全子数组的数目

  • 标签:滑动窗口、散列表

T3. 包含三个字符串的最短字符串

  • 标签:贪心、全排列、前后缀分解、KMP

T4. 统计范围内的步进数字数

  • 标签:数位 DP、记忆化


T1. 满足目标工作时长的员工数目

https://leetcode.cn/problems/number-of-employees-who-met-the-target/

题解(模拟)

简单模拟题。

class Solution {
public:int numberOfEmployeesWhoMetTarget(vector<int>& hours, int target) {int ret = 0;for (int i = 0; i < hours.size(); i++) {if (hours[i] >= target) ret++;}return ret;}
};
class Solution:def numberOfEmployeesWhoMetTarget(self, hours: List[int], target: int) -> int:return sum(e >= target for e in hours)

复杂度分析:

  • 时间复杂度: O ( n ) O(n) O(n) 线性扫描;
  • 空间复杂度: O ( 1 ) O(1) O(1) 仅使用常量级别空间。

T2. 统计完全子数组的数目

https://leetcode.cn/problems/count-complete-subarrays-in-an-array/

题解一(枚举子数组 + 散列表)

枚举子数组,求满足条件的子数组数

class Solution {
public:int countCompleteSubarrays(vector<int>& nums) {int n = nums.size();int ret = 0;// 目标元素个数int target = unordered_set<int>(nums.begin(), nums.end()).size();// 枚举子数组for (int i = 0; i < nums.size(); i++) {unordered_set<int> curSet;for (int j = i; j < nums.size(); j++) {curSet.insert(nums[j]);if (curSet.size() == target) {ret += n - j;break;}}}return ret;}
};

复杂度分析:

  • 时间复杂度: O ( n 2 ) O(n^2) O(n2) 枚举子数组时间;
  • 空间复杂度: O ( n ) O(n) O(n) 散列表空间。

题解二(滑动窗口 + 散列表)

在题解一中,当子数组的满足条件时,我们不再需要扩展右指针 j,其实左指针 i 也类似。当存在子数组 [i, j] 满足条件时,我们可以收缩左指针到 [i+1, j],如果子数组依然满足条件,则可以继续记录子数组个数 n - j 个。

class Solution {
public:int countCompleteSubarrays(vector<int>& nums) {int n = nums.size();int ret = 0;// 目标元素个数int target = unordered_set<int>(nums.begin(), nums.end()).size();// 滑动窗口unordered_map<int, int> cnts;int i = 0;for (int j = 0; j < nums.size(); j++) {cnts[nums[j]]++;while (cnts.size() == target) {ret += n - j;if (--cnts[nums[i]] == 0) cnts.erase(nums[i]);i++;}}return ret;}
};

复杂度分析:

  • 时间复杂度: O ( n ) O(n) O(n) 滑动窗口的 i 指针和 j 指针最多移动 n 次;
  • 空间复杂度: O ( n ) O(n) O(n) 散列表空间。

相似题目:

  • 3. 无重复字符的最长子串
  • 159. 至多包含两个不同字符的最长子串
  • 209. 长度最小的子数组
  • 424. 替换后的最长重复字符
  • 713. 乘积小于 K 的子数组
  • 992. K 个不同整数的子数组

T3. 包含三个字符串的最短字符串

https://leetcode.cn/problems/shortest-string-that-contains-three-strings/

题解一(贪心)

首先,合并字符串 a 和字符串 b 可以用前后缀分解来模拟:a 的最长后缀与 b 的最长前缀匹配,得到的合并字符串是最短的。而对于目标答案的合并方案来说,必然是 [a, b, c] 的全排列中的一种:

  • a + b + c
  • a + c + b
  • b + a + c
  • b + c + a
  • c + a + b
  • c + b + a

虽然,严谨来说局部贪心是错误的(即先将 a 和 b 合并得到最短字符串 ab,再将 ab 与 c 合并)。例如以下测试用例,这说明在第一次合并中选择最短的字符串,不一定是全局最短的字符串。但是,最优解必然可以通过全排列中的其他方案获得。因此,直接使用 “局部贪心” 即可。

a = "cdaa"
b = "aaef"
c = "daaae"
# a + b + c 其中 a + b = "cdaaef",无法与 c 合并得到最优解 “cdaaaef”
# a + c + b 可以得到最优解 “cdaaaef”
class Solution:def minimumString(self, a: str, b: str, c: str) -> str:def merge(a: str, b: str) -> str:if b in a: return afor i in range(min(len(a), len(b)), 0, -1):# 前后缀对比if a[-i:] == b[:i]: return a + b[i:]return a + bret = ""for a, b, c in permutations((a, b, c)): temp = merge(merge(a,b), c)# 优先最短字符串,再考虑字典序最小if (ret == "" or len(temp) < len(ret) or (len(temp) == len(ret) and temp < ret)):ret = tempreturn ret

复杂度分析:

  • 时间复杂度: O ( n 2 ) O(n^2) O(n2) 单次合并的时间复杂度是 O ( n 2 ) O(n^2) O(n2)
  • 空间复杂度: O ( n ) O(n) O(n) 临时字符串空间。

题解二(KMP)

题解一时间复杂度的瓶颈在 merge 函数,对于两个字符串的最长的前后缀匹配长度,这正好就是 KMP 算法中求解 next 数组的步骤,而 KMP 算法的时间复杂度是 O(n),存在优化空间。

  • next[i] 的含义:s[:i] 的后缀与前缀的最长匹配长度

另外还有一个细节,在合并 a 和 b 时我们在中间插入分隔符 “#”,这是为了避免匹配长度大于 a 或 b的长度。例如:

a = "cac"
b = "aca"
# 那么 a + b = "cacaca" 会出现匹配长度大于 a 或 b的长度
class Solution:def minimumString(self, a: str, b: str, c: str) -> str:def merge(a: str, b: str) -> str:if b in a: return a# 拼接字符串,以计算 b 的后缀与 a 的前缀的匹配长度s = a + "#" + b# KMP 求 next 数组j, next = 0, [0] * len(s)for i in range(1, len(s)):while j > 0 and s[i] != s[j]:j = next[j - 1]if s[i] == s[j]:j += 1next[i] = j# next[-1]: s[-1] 的最长匹配前缀return b + a[next[-1]:]ret = ""for a, b, c in permutations((a, b, c)): temp = merge(merge(a,b), c)# 优先最短字符串,再考虑字典序最小if (ret == "" or len(temp) < len(ret) or (len(temp) == len(ret) and temp < ret)):ret = tempreturn ret

复杂度分析:

  • 时间复杂度: O ( n ) O(n) O(n) 单次合并的时间复杂度是 O ( n ) O(n) O(n)
  • 空间复杂度: O ( n ) O(n) O(n) 临时字符串空间。

T4. 统计范围内的步进数字数目

https://leetcode.cn/problems/count-stepping-numbers-in-range/

题解(数位 DP + 记忆化)

相对标准的数位 DP 模板题。

  • 1、数位 DP: 我们定义 dp[i, pre, isNumber, isLimit] 表示从第 i 位开始的合法方案数,其中:
    • pre 表示上一个数位选择的值;
    • isNumber 表示已填数位是否构造出合法数字;
    • isLimit 表示当前数位是否被当前数位的最大值约束。
  • 2、差值: 由于题目输入是字符串,要计算出 [low, high] 之间的合法方案数,我们可以计算出 [0, high] 和 [0, low] 之间合法方案数的差值,我们可以再单独判断 low 是否合法。
  • 3、记忆化: 对于相同 dp[i, …] 子问题,可能会重复计算,可以使用记忆化优化时间复杂度:
class Solution {val MOD = 1000000007fun countSteppingNumbers(low: String, high: String): Int {// 数位 DPreturn ((f(high) - f(low) + if (check(low)) 1 else 0) + MOD) % MOD}private fun f(num: String): Int {val memo = Array(num.length) { Array(10) { IntArray(2) { -1 } } }return dp(memo, 0, num, '0', false, true)}private fun check(num: String) : Boolean {for (i in 1 until num.length) {if (Math.abs(num[i] - num[i - 1]) != 1) return false}return true}// dp[i, pre, isNumber]private fun dp(memo: Array<Array<IntArray>>, i: Int, high: String, pre: Char, isNumber: Boolean, isLimit: Boolean): Int {// 终止条件if (i == high.length) {return if (isNumber) 1 else 0}// 读备忘录if (!isLimit && -1 != memo[i][pre - '0'][if (isNumber) 1 else 0]) {return memo[i][pre - '0'][if(isNumber) 1 else 0]}var ret = 0val lower = '0'val upper = if (isLimit) high[i] else '9'for (choice in lower .. upper) {if (!isNumber || Math.abs(choice - pre) == 1) {ret = (ret + dp(memo, i + 1, high, choice, isNumber || choice != '0', isLimit && choice == upper)) % MOD}}if (!isLimit) memo[i][pre - '0'][if (isNumber) 1 else 0] = retreturn ret}
}

复杂度分析:

  • 时间复杂度: O ( n C ⋅ C ) O(nC·C) O(nCC) 其中 n 为数位长度,C 为字符集大小 ,总共有 n·C 个子状态,每个子状态的时间复杂度是 O ( C ) O(C) O(C),整体时间复杂度是 O ( n ⋅ C 2 ) O(n·C^2) O(nC2)
  • 空间复杂度: O ( n ⋅ C ) O(n·C) O(nC) 记忆化空间。

推荐阅读

LeetCode 上分之旅系列往期回顾:

  • LeetCode 单周赛第 355 场 · 两题坐牢,菜鸡现出原形
  • LeetCode 单周赛第 354 场 · 摩尔投票派上用场
  • LeetCode 双周赛第 109 场 · 按部就班地解决动态规划问题
  • LeetCode 双周赛第 107 场 · 很有意思的 T2 题

⭐️ 永远相信美好的事情即将发生,欢迎加入小彭的 Android 交流社群~

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/17648.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

day50-Insect Catch Game(捉虫游戏)

50 天学习 50 个项目 - HTMLCSS and JavaScript day50-Insect Catch Game&#xff08;捉虫游戏&#xff09; 效果 index.html <!DOCTYPE html> <html lang"en"><head><meta charset"UTF-8" /><meta name"viewport"…

codeblocks编译lvgl报错

codeblocks编译lvgl报错 1.报错内容2.解决方案3.lvgl本身代码报错 1.报错内容 error: unknown type name _In_opt_ ...2.解决方案 官网下载&#xff1a; 这里的Compiler’s installation directory 选择自带的 3.lvgl本身代码报错 undefined reference to _lv_utils_bse…

面试总结-Redis篇章(九)——Redis主从复制、主从数据同步原理

Redis其他面试问题 主从复制单节点Redis的并发能力是有上限的&#xff0c;要进一步提高Redis的并发能力&#xff0c;就需要搭建主从集群&#xff0c;实现读写分离主节点主要进行客户端的写操作&#xff0c;从节点进行客户端的读操作&#xff0c;因为Redis一直都是读多写少&…

UE5.1.1 创建C++项目失败

因一直使用Unity开发环境&#xff0c;安装Unreal后&#xff0c;并未详细配置过其开发环境&#xff0c;默认创建蓝图工程无异常&#xff0c;但创建UE C项目时总共遇到两个错误&#xff1a; 错误一 Running /Epic/UE/UE_5.1/Engine/Build/BatchFiles/Build.bat -projectfiles -…

<C++>二、类和对象-构造函数

1.类的6个默认成员函数 如果一个类中什么成员都没有&#xff0c;简称为空类。 空类中真的什么都没有吗&#xff1f;并不是&#xff0c;任何类在什么都不写时&#xff0c;编译器会自动生成以下6个默认成员函数。 默认成员函数&#xff1a;用户没有显式实现&#xff0c;编译器会生…

【Linux多线程】死锁问题介绍

死锁 &#x1f96d;什么是死锁&#x1f965;死锁产生的条件&#x1f95d;如何避免死锁&#x1f345; 避免死锁算法 &#x1f96d;什么是死锁 死锁是多线程或多进程编程中的一种常见问题&#xff0c;指的是两个或多个线程&#xff08;或进程&#xff09;相互等待对方持有的资源&…

搜索二叉树_SearchBinaryTree

目录 搜索二叉树的原理 搜索二叉树的搜索时间复杂度 二叉搜索树实现_key 模型 节点 构造函数 查找 中序遍历 插入 循环 递归 删除 循环 1.删除叶子节点 2.删除有一个孩子的节点 3.左右孩子都不为空 递归 析构函数 拷贝构造 operator key_value 模型 节点 …

Mysql触发器

1.触发器 触发器是与表有关的数据库对象&#xff0c;指在 insert / update / delete 之前或之后&#xff0c;触发并执行触发器中定义的SL语句集合。触发器的这种特性可以协助应用在数据库端确保数据的完整性&#xff0c;日志记录&#xff0c;数据校验等操作。 使用别名 OLD 和 …

mysql安装教程保姆级

MySQL免安装本地运行 1.下载MySQL2.创建install.bat3.init.sql 初始创建4.环境变量配置5.运行 install.bat 管理员权限运行6.连接成功遇到的问题 1.下载MySQL ①地址&#xff1a;https://downloads.mysql.com/archives/community/ ②解压 2.创建install.bat 放在mysql>b…

算法综合篇专题一:双指针问题

"就算没有看清那株灿烂的花蕊&#xff0c;也应该放声歌颂赞美鲜红的玫瑰" 1、移动零 (1) 题目解析 (2) 算法原理 class Solution { public:void moveZeroes(vector<int>& nums) {for(int cur0,dest-1;cur<nums.size();cur){if(nums[cu…

java设计模式-建造者(Builder)设计模式

介绍 Java的建造者&#xff08;Builder&#xff09;设计模式可以将产品的内部表现和产品的构建过程分离开来&#xff0c;这样使用同一个构建过程来构建不同内部表现的产品。 建造者设计模式涉及如下角色&#xff1a; 产品&#xff08;Product&#xff09;角色&#xff1a;被…

前端工程化最佳实践:项目结构、代码规范和文档管理

文章目录 前端工程化最佳实践项目结构设计与组织文档管理和注释规范国际化和本地化实践 前端工程化的未来发展趋势前端工程化领域的最新技术和工具WebAssembly 和前端性能优化可持续性和可访问性的趋势 总结前端工程化的关键知识点前端工程化对项目和团队的价值 前端工程化最佳…

《golang设计模式》第一部分·创建型模式-01-单例模式(Singleton)

文章目录 1. 概述1.1 目的1.2 实现方式 2. 代码示例2.1 设计2.2 代码 1. 概述 1.1 目的 保证类只有一个实例有方法能让外部访问到该实例 1.2 实现方式 懒汉式 在第一次调用单例对象时创建该对象&#xff0c;这样可以避免不必要的资源浪费 饿汉式 在程序启动时就创建单例对象…

卷积神经网络

目录 注意&#xff1a;有参数计算的才叫层 1.应用 1.1分类和检索 1.2超分辨率重构 1.3医学任务 1.4无人驾驶 1.5人脸识别 2.卷积 2.1卷积神经网络和传统网络的区别 2.2整体框架 2.3理解卷积&#xff08;重点&#xff09; 2.4为何要进行多层卷积 2.5卷积核的参数 2.6…

C++STL库中的list

文章目录 list的介绍及使用 list的常用接口 list的模拟实现 list与vector的对比 一、list的介绍及使用 1. list是可以在常数范围内在任意位置进行插入和删除的序列式容器&#xff0c;并且该容器可以前后双向迭代。 2. list的底层是双向带头循环链表结构&#xff0c;双向带头循…

基于ssm+mysql+jsp高校疫情防控出入信息管理系统

基于ssmmysqljsp高校疫情防控出入信息管理系统 一、系统介绍二、功能展示1.登陆2.教师管理3.学生管理4.打卡记录管理5.学生申请通行证6.通行证管理7.留言信息管理8.公告类型管理9.公告管理 四、获取源码 一、系统介绍 学生 : 个人中心、打卡记录管理、学生申请通行证、通行证管…

<C++> STL_string

目录 1.string类 2.string类的接口 2.1 成员函数 2.1.1 string构造函数 2.1.2 string赋值运算 2.1.3 string析构函数 2.2 string对象访问以及迭代器 2.2.1 string的遍历方式 2.2.2 迭代器的使用 2.2.3 const_迭代器的使用 2.2.4 at 2.2.5 back和front 2.3 string容…

Docker基础命令(一)

Docker使用1 一、运行终端 打开终端&#xff0c;输入docker images &#xff0c;如果运行正常&#xff0c;表示docker已经可以在本电脑上使用了 二、docker常用命令 指令说明docker images查看已下载的镜像docker rmi 镜像名称:标签名删除已下载的镜像docker search 镜像从官…

Java如何实现将类文件打包为jar包

目录 将类文件打包为jar包 1.写类文件2.编译3.测试4.打jar包jar包应该怎么打&#xff1f; 1.首先确保你的项目2.选中你的项目,点右键3.选择runnable jar file4.如下图,直接看图5.然后点finish 将类文件打包为jar包 为实际项目写了一个工具类&#xff0c;但是每次使用时都需要…

xcode中如何显示文件后缀

xcode14.3 用不惯mac电脑真恶心&#xff0c;改个显示文件后缀找半天 1、首先双击打开xcode软件 2、此时&#xff0c;电脑左上角出现xcode字样(左上角如果看不到xcode字样&#xff0c;再次点击xcode软件弹出来就有了)&#xff0c;鼠标右键它&#xff0c;点击setting或者Prefere…