持续创作,加速成长!这是我参与「掘金日新计划 · 6 月更文挑战」的第9天,点击查看活动详情
现在前端很多岗位面试需要有一定的算法基础,或者说经常刷算法的会优先考虑。
因此每天刷刷LeetCode非常有必要
在这之前我也刷过一些算法题,也希望以后也坚持刷,跟某掘友一样,我也想刷穿 LeetCode
一、题目描述
给定一个表示代码片段的字符串,你需要实现一个验证器来解析这段代码,并返回它是否合法。合法的代码片段需要遵守以下的所有规则:
代码必须被合法的闭合标签包围。否则,代码是无效的。 闭合标签(不一定合法)要严格符合格式:<TAG_NAME>TAG_CONTENT</TAG_NAME>。其中,<TAG_NAME>是起始标签,</TAG_NAME>是结束标签。起始和结束标签中的 TAG_NAME 应当相同。当且仅当 TAG_NAME 和 TAG_CONTENT 都是合法的,闭合标签才是合法的。 合法的 TAG_NAME 仅含有大写字母,长度在范围 [1,9] 之间。否则,该 TAG_NAME 是不合法的。 合法的 TAG_CONTENT 可以包含其他合法的闭合标签,cdata (请参考规则7)和任意字符(注意参考规则1)除了不匹配的<、不匹配的起始和结束标签、不匹配的或带有不合法 TAG_NAME 的闭合标签。否则,TAG_CONTENT 是不合法的。 一个起始标签,如果没有具有相同 TAG_NAME 的结束标签与之匹配,是不合法的。反之亦然。不过,你也需要考虑标签嵌套的问题。 一个<,如果你找不到一个后续的>与之匹配,是不合法的。并且当你找到一个<或</时,所有直到下一个>的前的字符,都应当被解析为 TAG_NAME(不一定合法)。 cdata 有如下格式:。CDATA_CONTENT 的范围被定义成 之间的字符。 CDATA_CONTENT 可以包含任意字符。cdata 的功能是阻止验证器解析CDATA_CONTENT,所以即使其中有一些字符可以被解析为标签(无论合法还是不合法),也应该将它们视为常规字符。
二、思路分析
本质上这应该是一个词法分析器,我现在把应该写成状态机的部分简化了,手动的维护了几个状态 是否进入cdata,当前tag栈,当前未解析完成的tag name,当前的tag解析状态 然后状态转移进行代码分析。
基本思路是把规则检查的部分从代码分析的过程中抽离出来,使代码分析的过程更加清晰,同时通过捕获异常的方式来接收代码分析过程中违反规则的错误,以简化规则检查的复杂程度。
三、代码实现
var isValid = function(code) {
// 设置多个检查函数 检查失败就抛出异常 在运行时catch异常
function checkBeginning(code){ // rule 1
if(!(code[0] === '<' && code[1] !== '/' && code[1] !== '!')){
throw 'beginning error';
}
}
function checkEndding(code){ //rule 1
if(code[code.length - 1] !== '>'){
throw 'endding error';
}
}
function checkTagName(name){ // rule 3
if(name.length < 1 || name.length > 9){
throw 'tag name length error';
}
for(let i = 0; i < name.length; ++i){
if(name[i].charCodeAt() < ('A').charCodeAt() || name[i].charCodeAt() > ('Z').charCodeAt()){
throw 'tag name char error';
}
}
}
function checkTagClosed(tagStack, tag){// rule 2
if(tagStack[tagStack.length - 1] !== tag){
throw 'tag match error';
}
}
function checkOneTagRoot(tagStack, code, i){// rule 1
if(tagStack.length === 0 && code.length - i > 1){
throw 'tag one root error';
}
}
try{
checkBeginning(code);
checkEndding(code);
// 需要记录几个状态, 是否进入cdata,当前tag栈,当前未解析完成的tag name,当前的tag解析状态
let cdataState = false;
let tagStack = [];
let tagName = '';
let tagState = null; // null begin end
let i = 0;
while(i < code.length){
let current = code[i];
if(cdataState){ // 处于 cdataState
if(current ===']' && code.substr(i, 3) === ']]>'){
cdataState = false;
i += 3;
}else{
++i;
}
}else{
if(tagState){
if(current === '>'){
checkTagName(tagName);
if(tagState === 'begin'){
tagStack.push(tagName);
}else{
checkTagClosed(tagStack, tagName);
tagStack.pop();
checkOneTagRoot(tagStack, code, i);
}
tagState = null;
++i;
}else{
tagName += current;
++i;
}
}else{
if(current === '<'){
if(code[i + 1] === '/'){
tagState = 'end';
tagName = '';
i += 2;
}else if(code.substr(i, 9) === '<![CDATA['){
cdataState = true;
i += 9;
}else{
tagState = 'begin';
tagName = '';
i += 1;
}
}else{
++i;
}
}
}
}
if(cdataState === false && tagState === null && tagStack.length === 0){
return true;
}
return false;
}catch(e){
// console.log(e)
return false;
}
};
四、总结
以上就是本道题的所有内容了,本系列会持续更,欢迎点赞、关注、收藏,另外如有其他的问题,欢迎下方留言给我,我会第一时间回复你,感谢~