【我也想刷穿 LeetCode啊】591. 标签验证器

67 阅读3分钟

持续创作,加速成长!这是我参与「掘金日新计划 · 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;
    }
};

四、总结

以上就是本道题的所有内容了,本系列会持续更,欢迎点赞、关注、收藏,另外如有其他的问题,欢迎下方留言给我,我会第一时间回复你,感谢~