0502 | 591. 标签验证器
正则:
- 正则表达式不要背.
- 留个标记,winter 玩具浏览器 toy browser 可以按照这个思路优化。
1. 正则中 .*?
是什么意思?
.
: 匹配 1 个字符,匹配 任何 单个字符;*
:匹配 ≥ 0 个字符,匹配 在它之前的那个 字符。?
:匹配 0|1 个字符,匹配 在它之前的那个 字符。
所以:
.*
:匹配任何字符 ≥ 0 个;.?
:匹配任何字符 0|1 个;
那么:
.*?
: 匹配表示匹配任意字符到下一个符合条件的字符。
- 例子:正则表达式
a.*?xxx
可以匹配abxxx
axxxxx
abbbbbxxx
。
.*
:尽可能匹配符合条件的字符,尽可能长,贪婪模式
例子:
- 字符串:
<img src="test.jpg" width="60px" height="80px"/>
如果要匹配 src 的属性值; src=".*"
, 匹配结果是:src="test.jpg" width="60px" height="80px"
- 贪婪模式:意思是从
="
往后匹配,直到最后一个"
,匹配结束。
- 贪婪模式:意思是从
src=".*?"
,匹配结果是:src="test.jpg"
- 懒惰模式:匹配到第一个
"
就结束了一次匹配,不会继续向后匹配。
- 懒惰模式:匹配到第一个
2. 正则中 [A-Z]{1,9}
是什么意思?
-
正则中的方括号
[]
表示集合,也就是说只要字符串内容是 A-Z 中的任意大写字母都可以。 -
正则中的大括号
{}
表示次数,也就是匹配{}
的前一个字符的次数:- {x} // x次 - {min, max} // 介于 min 次到 max 次之间 - {min, } // 至少 min 次 - {0, max} // 至多 max 次 - ? // 0|1 次 - * // ≥ 0 次, 也就是不限制次数,尽可能匹配最长 - + // ≥ 1 次
/a{3}/
表示匹配 a 3次
所以:原题目中的意思是匹配一个字符串,长度是 1-9 (含9) 个字符,这些字符必须是大写字母。
方法一:使用正则
var isValid = function (code) {
// cdata 内容
let reg1 = new RegExp(/<!\[CDATA\[.*?\]\]>|t/g);
// 开始、结束标签 \1 是一个回溯引用,引用了开始标签`<...>`的内容。
let reg2 = new RegExp(/<([A-Z]{1,9})>[^<]*<\/\1>/);
// 匹配cdata,然后置换为 - ;
code = code.replace(reg1, "-");
let temp = '';
while (temp != code) {
temp = code;
code = code.replace(reg2, "t");
console.log(code);
}
return code == 't';
};
方法二:使用状态机
- leetcode解析:链接.
- 栈 + 字符串遍历
var isValid = function (code) {
const n = code.length;
const tags = [];
let i = 0;
while (i < n) {
// 开始标签、结束标签、CDATA开头
if (code[i] === '<') {
if (i === n - 1) {
return false;
}
// 结束标签
if (code[i + 1] === '/') {
const j = code.indexOf('>', i);
if (j < 0) {
return false;
}
const tagname = code.slice(i + 2, j);
// 找到结束标签,让栈中的开始标签出栈,比对是否相等。
if (tags.length === 0 || tags[tags.length - 1] !== tagname) {
return false;
}
tags.pop();
i = j + 1;
if (tags.length === 0 && i !== n) {
return false;
}
// CDATA 内容
} else if (code[i + 1] === '!') {
if (tags.length === 0) {
return false;
}
if (i + 9 > n) {
return false;
}
const cdata = code.slice(i + 2, i + 9);
if ("[CDATA[" !== cdata) {
return false;
}
const j = code.indexOf("]]>", i);
if (j < 0) {
return false;
}
i = j + 1;
// 开始标签
} else {
const j = code.indexOf('>', i);
if (j < 0) {
return false;
}
const tagname = code.slice(i + 1, j);
// 判断开始标签是否符合长度 1-9 要求
if (tagname.length < 1 || tagname.length > 9) {
return false;
}
// 判断开始标签是否符合字母 A-Z 要求
for (let k = 0; k < tagname.length; ++k) {
if (!(tagname[k] >= 'A' && tagname[k] <= 'Z')) {
return false;
}
}
// 开始标签符合要求,写入 stack 中。
tags.push(tagname);
i = j + 1;
}
} else {
// 如果没有 '<' 则只有两种情况:
// 1. 处在一个开始标签內,且不在CDATA内:'<HTML> ......',这时候让 i++;
// 2. 不在一个开始标签内,'....<HTML>aaa</HTML>',这时候不符合标准,返回false。通过判断 tags.length 是否为0可以知道。
if (tags.length === 0) {
return false;
}
++i;
}
}
return tags.length === 0;
};