我对一类常考算法面试题的详细分析
给你一个字符串 s ,请你返回满足以下条件的最长子字符串的长度:每个元音字母,即 'a','e','i','o','u' ,在子字符串中都恰好出现了偶数次。
示例 1:
输入:s = "leetminicoworoep"
输出:13
解释:最长子字符串是 "leetminicowor" ,它包含 e,i,o 各 2 个,以及 0 个 a,u 。
示例 2:
输入:s = "leetcodeisgreat"
输出:5
解释:最长子字符串是 "leetc" ,其中包含 2 个 e 。
示例 3:
输入:s = "bcbcbc"
输出:6
解释:这个示例中,字符串 "bcbcbc" 本身就是最长的,因为所有的元音 a,e,i,o,u 都出现了 0 次。
提示:
1 <= s.length <= 5 x 10^5
s 只包含小写英文字母。
来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/find-the-longest-substring-containing-vowels-in-even-counts
2 分析过程
创建一个状态机:
- 对于非元音字符放置到位置0处,
- 元音字符
'a'
放置到位置1处, - 元音字符
'e'
放置到位置2处, - 元音字符
'i'
放置到位置4处, - 元音字符
'o'
放置到位置8处, - 元音字符
'u'
放置到位置16处
元音字符之所以放到1,2,4,8,16,是要为位运算创造条件,二进制表示中这些数字都只有1位为1,其他位置都为0. 很明显,为1的位置是标志位。
以处理leetcode
字符串为例:
状态机有如下6个取值,非元音字符放置到0处:
处理第二个字符e
时,放置到2处:
第三个字符又是e
,再次放置到2处:
下面又是两个非元音字符,到字符c
为止,字符串leetc
就是满足题意(单个元音字符出现偶数次)的最大子字符串。
判断元音字符出现偶数次的方法:二进制表示下,且6个值(0,1,2,4,8,16)都只有一个位为1,所以使用异或运算,某个元音字符出现偶数次时,此位最终状态必然为0;奇数次时最终值必然为1.
接下来,处理下一个字符o
,但是后面没有字符o
,只出现1次,不满足题意:
接下来一样方法处理剩余字符,所以整个字符串满足题意的最长子串为:leetc
如果字符串修改为:leetcodoe
,满足题意的最长子串:leetcodo
.
3 代码
基于以上分析,再看下面代码就不难理解:
class Solution(object):
def findTheLongestSubstring(self,s):
state, statedict = 0, {0:-1} #设置此初始值决定下面的代码 i-statedict[state] 这样写
maxlen = 0
codedict = {'a':1,'e':2,'i':4,'o':8,'u':16}
for i,c in enumerate(s):
if c in codedict:
state ^= codedict[c]
if state in statedict:
maxlen = max(maxlen,i-statedict[state])
else:
statedict[state] = i # 记忆新的状态值,二进制位下,可能会出现类似"第1位或第3位为1"的32种组合
return maxlen
statedict
设置{0:-1}
初始值,也是很有讲究、很巧妙的,决定了下面的代码 i-statedict[state]
这样写
statedict[state] = i
记忆新的状态值,二进制位下,可能会出现类似第1位或第3位为1的32种组合。
4 扩展
今天题目与Day50的思路极为类似,Day50: 连续数组,可以归纳为前缀和问题。
此类问题关键是想办法巧妙的处理各种状态,区分各种状态。记忆某种状态,中间经历某种变换或抵消操作后,出现了状态字典里的某个状态,表明找到满足题意的前缀。
比如,字符串lee
,第一个状态是l
,第二个是le
,第三个状态又是l
,因为2个e
能抵消。因此,满足题意的最长子串长度为3.
字符串oeo
,第一个状态是o
,第二个状态oe
,第三个状态是e
,两个o
抵消,因此没有重复状态。因此,满足题意的最长子串长度为0.
- C#代码也VB
- Docker容器学习梳理--SSH方式登陆容器
- Docker网络解决方案-Flannel部署记录
- Nginx的location配置规则梳理
- 统计代码行数的方法梳理
- 如何在不影响asp.net默认安全性的前提下使用ckeditor/fckeditor?
- Linux下防御DDOS攻击的操作梳理
- Android新手之旅(8) ListView的使用
- 更换Ubuntu源为国内源的操作记录
- Android新手之旅(8) ListView的使用
- CKEditor/CKFinder升级心得
- Docker容器学习梳理-Dockerfile构建镜像
- 再谈web开中几种经典的大文件上传组件
- Nginx负载均衡中后端节点服务器健康检查的操作梳理
- JavaScript 教程
- JavaScript 编辑工具
- JavaScript 与HTML
- JavaScript 与Java
- JavaScript 数据结构
- JavaScript 基本数据类型
- JavaScript 特殊数据类型
- JavaScript 运算符
- JavaScript typeof 运算符
- JavaScript 表达式
- JavaScript 类型转换
- JavaScript 基本语法
- JavaScript 注释
- Javascript 基本处理流程
- Javascript 选择结构
- Javascript if 语句
- Javascript if 语句的嵌套
- Javascript switch 语句
- Javascript 循环结构
- Javascript 循环结构实例
- Javascript 跳转语句
- Javascript 控制语句总结
- Javascript 函数介绍
- Javascript 函数的定义
- Javascript 函数调用
- Javascript 几种特殊的函数
- JavaScript 内置函数简介
- Javascript eval() 函数
- Javascript isFinite() 函数
- Javascript isNaN() 函数
- parseInt() 与 parseFloat()
- escape() 与 unescape()
- Javascript 字符串介绍
- Javascript length属性
- javascript 字符串函数
- Javascript 日期对象简介
- Javascript 日期对象用途
- Date 对象属性和方法
- Javascript 数组是什么
- Javascript 创建数组
- Javascript 数组赋值与取值
- Javascript 数组属性和方法
- 多媒体程序开发
- 本地 IDE 已废!编辑器大结局!GitHub 的云 VSCode 实测
- 实战 | Python 编写端口扫描器
- 我这几年踩过的十个坑,每一条都是血泪教训
- 在 Python 中如何快速创建一个只读字典?
- 现场打脸:如何使用Selenium批量上传文件?
- 一日一技:FastAPI如何关闭接口文档?
- 什么叫做类比,为什么有些 Python 入门教程结构不合理?
- 贼好用的 Java 工具类库,墙裂推荐!
- 万字长文,Thread 类源码解析!
- lintCode 31 题解
- JDK1.8HashMap源码学习-put操作以及扩容(二)
- Python 中的数字到底是什么?
- 详解 Python 的二元算术运算,为什么说减法只是语法糖?
- 详解增强算术赋值:“-=”操作是怎么实现的?