跳到主要内容

97 篇博文 含有标签「字符串」

查看所有标签

· 阅读需 4 分钟

1、题干

某种外星语也使用英文小写字母,但可能顺序 order 不同。字母表的顺序(order)是一些小写字母的排列。

给定一组用外星语书写的单词 words,以及其字母表的顺序 order,只有当给定的单词在这种外星语中按字典序排列时,返回 true;否则,返回 false

 

示例 1:

输入:words = ["hello","leetcode"], order = "hlabcdefgijkmnopqrstuvwxyz"
输出:true
解释:在该语言的字母表中,'h' 位于 'l' 之前,所以单词序列是按字典序排列的。

示例 2:

输入:words = ["word","world","row"], order = "worldabcefghijkmnpqstuvxyz"
输出:false
解释:在该语言的字母表中,'d' 位于 'l' 之后,那么 words[0] > words[1],因此单词序列不是按字典序排列的。

示例 3:

输入:words = ["apple","app"], order = "abcdefghijklmnopqrstuvwxyz"
输出:false
解释:当前三个字符 "app" 匹配时,第二个字符串相对短一些,然后根据词典编纂规则 "apple" > "app",因为 'l' > '∅',其中 '∅' 是空白字符,定义为比任何其他字符都小(更多信息)。

 

提示:

  • 1 <= words.length <= 100
  • 1 <= words[i].length <= 20
  • order.length == 26
  • 在 words[i] 和 order 中的所有字符都是英文小写字母。

 

注意:本题与主站 953 题相同: https://leetcode-cn.com/problems/verifying-an-alien-dictionary/

解题思路

思路 将words中的所有字符替换为正常字典序的字符,然后使用比较运算符(大于号、小于号等)去比较所有words即可。

例子 words = ["hl","la"], order = "hlabcdefgijkmnopqrstuvwxyz"

  • words中的字符替换为正常字典序的字符,结果为:words = ["ab","bc"]
  • 使用比较运算符比较所有words"ab"<"bc"结果为true,最终返回true

步骤

  • 创建哈希映射map,遍历order,获得外星字典序(键)与正常字典序(值)的映射
  • 遍历words中的所有字符串
    • words[i]中的字符替换为正常字典序的字符
    • 使用比较运算符比较words[i]words[i - 1]的大小
    • 如果i && words[i] < words[i - 1]则不符合外星字典序直接返回false
  • 函数运行到结尾,则说明符合外星字典序,返回true

代码

var isAlienSorted = function (words, order) {
const map = new Map();
for (let i = 0; i < order.length; i++) map.set(order[i], String.fromCharCode(97 + i));

for (let i = 0; i < words.length; i++) {
words[i] = [...words[i]].reduce((a, c) => a + map.get(c), '');
if (i && words[i] < words[i - 1]) return false;
}

return true;
};

· 阅读需 4 分钟

1、题干

给定一个 24 小时制(小时:分钟 "HH:MM")的时间列表,找出列表中任意两个时间的最小时间差并以分钟数表示。

 

示例 1:

输入:timePoints = ["23:59","00:00"]
输出:1

示例 2:

输入:timePoints = ["00:00","23:59","00:00"]
输出:0

 

提示:

  • 2 <= timePoints.length <= 2 * 104
  • timePoints[i] 格式为 "HH:MM"

 

注意:本题与主站 539 题相同: https://leetcode-cn.com/problems/minimum-time-difference/

解法1

执行结果

0.png

解题思路

前置处理:先排除几种特殊情况:timePoints.length > 1440timePoints.length > 720new Set(timePoints).size < timePoints.length

  • timePoints.length > 1440new Set(timePoints).size < timePoints.length,则必定有重复值,最小差值为0
  • timePoints.length > 720最小差值为1。因为分钟数最多1440种情况,如果分钟数量超过一半,则必定有分钟数相邻

后续步骤:把时间字符串转为分钟数,直接排序并计算最小差值

  • timePoints中的时间字符串HH:MM转成分钟数(0-1400),构建出新的分钟数数组times
  • times进行升序排序
  • times数组末尾推入1440+times[0](最小分钟数),使得最小时间和最大时间的差值计算常规化
  • 对相邻分钟数求差值,最后返回最小差值

整体时间复杂度为O(C)O(C),其中2<=C<=720log7202<=C<=720*log720

代码

var findMinDifference = function (timePoints) {
if (timePoints.length > 1440 || new Set(timePoints).size < timePoints.length) return 0;
if (timePoints.length > 720) return 1;

const times = timePoints.map(p => p.split(':').reduce((a, c, i) => a + (+c) * (i ? 1 : 60), 0));
times.sort((a, b) => a - b).push(1440 + times[0]);

return times.reduce((a, c, i) => i ? Math.min(a, c - times[i - 1]) : a, 1440);
};

2、解法2

执行结果

1.png

解题思路

前置处理:先排除几种特殊情况:timePoints.length > 1440timePoints.length > 720new Set(timePoints).size < timePoints.length

  • timePoints.length > 1440new Set(timePoints).size < timePoints.length,则必定有重复值,最小差值为0
  • timePoints.length > 720最小差值为1。因为分钟数最多1440种情况,如果分钟数量超过一半,则必定有分钟数相邻

后续步骤:把时间字符串转为分钟数,使用计数排序并计算最小差值

  • 创建以分钟数为下标的数组times,将timePoints中的时间字符串HH:MM转成分钟数,对times中的分钟数进行计数
  • 对相邻分钟数求差值,计算出最小差值
  • 计算最小时间和最大时间的差值,并与上一步的最小值比较,最终结果是:Math.min(min, 1440 + minTime - maxTime);

整体时间复杂度为O(C)O(C),其中2<=C<=14402<=C<=1440

代码

var findMinDifference = function (timePoints) {
if (timePoints.length > 1440 || new Set(timePoints).size < timePoints.length) return 0;
if (timePoints.length > 720) return 1;

const times = new Array(1440).fill(0);
for (let p of timePoints) {
times[p.split(':').reduce((a, c, i) => a + (+c) * (i ? 1 : 60), 0)] += 1;
}

let min = 1440, prev = -1440, minTime;
for (let i = 0; i < times.length; i++) {
if (minTime === undefined && times[i]) minTime = i;
if (times[i]) min = Math.min(min, i - prev), prev = i;
}

return Math.min(min, 1440 + minTime - prev);
};

· 阅读需 4 分钟

1、题干

某种外星语也使用英文小写字母,但可能顺序 order 不同。字母表的顺序(order)是一些小写字母的排列。

给定一组用外星语书写的单词 words,以及其字母表的顺序 order,只有当给定的单词在这种外星语中按字典序排列时,返回 true;否则,返回 false

 

示例 1:

输入:words = ["hello","leetcode"], order = "hlabcdefgijkmnopqrstuvwxyz"
输出:true
解释:在该语言的字母表中,'h' 位于 'l' 之前,所以单词序列是按字典序排列的。

示例 2:

输入:words = ["word","world","row"], order = "worldabcefghijkmnpqstuvxyz"
输出:false
解释:在该语言的字母表中,'d' 位于 'l' 之后,那么 words[0] > words[1],因此单词序列不是按字典序排列的。

示例 3:

输入:words = ["apple","app"], order = "abcdefghijklmnopqrstuvwxyz"
输出:false
解释:当前三个字符 "app" 匹配时,第二个字符串相对短一些,然后根据词典编纂规则 "apple" > "app",因为 'l' > '∅',其中 '∅' 是空白字符,定义为比任何其他字符都小(更多信息)。

 

提示:

  • 1 <= words.length <= 100
  • 1 <= words[i].length <= 20
  • order.length == 26
  • 在 words[i] 和 order 中的所有字符都是英文小写字母。

 

注意:本题与主站 953 题相同: https://leetcode-cn.com/problems/verifying-an-alien-dictionary/

解题思路

思路 将words中的所有字符替换为正常字典序的字符,然后使用比较运算符(大于号、小于号等)去比较所有words即可。

例子 words = ["hl","la"], order = "hlabcdefgijkmnopqrstuvwxyz"

  • words中的字符替换为正常字典序的字符,结果为:words = ["ab","bc"]
  • 使用比较运算符比较所有words"ab"<"bc"结果为true,最终返回true

步骤

  • 创建哈希映射map,遍历order,获得外星字典序(键)与正常字典序(值)的映射
  • 遍历words中的所有字符串
    • words[i]中的字符替换为正常字典序的字符
    • 使用比较运算符比较words[i]words[i - 1]的大小
    • 如果i && words[i] < words[i - 1]则不符合外星字典序直接返回false
  • 函数运行到结尾,则说明符合外星字典序,返回true

代码

var isAlienSorted = function (words, order) {
const map = new Map();
for (let i = 0; i < order.length; i++) map.set(order[i], String.fromCharCode(97 + i));

for (let i = 0; i < words.length; i++) {
words[i] = [...words[i]].reduce((a, c) => a + map.get(c), '');
if (i && words[i] < words[i - 1]) return false;
}

return true;
};

· 阅读需 4 分钟

1、题干

给定一个 24 小时制(小时:分钟 "HH:MM")的时间列表,找出列表中任意两个时间的最小时间差并以分钟数表示。

 

示例 1:

输入:timePoints = ["23:59","00:00"]
输出:1

示例 2:

输入:timePoints = ["00:00","23:59","00:00"]
输出:0

 

提示:

  • 2 <= timePoints <= 2 * 104
  • timePoints[i] 格式为 "HH:MM"

 

注意:本题与主站 539 题相同: https://leetcode-cn.com/problems/minimum-time-difference/

解法1

执行结果

0.png

解题思路

前置处理:先排除几种特殊情况:timePoints.length > 1440timePoints.length > 720new Set(timePoints).size < timePoints.length

  • timePoints.length > 1440new Set(timePoints).size < timePoints.length,则必定有重复值,最小差值为0
  • timePoints.length > 720最小差值为1。因为分钟数最多1440种情况,如果分钟数量超过一半,则必定有分钟数相邻

后续步骤:把时间字符串转为分钟数,直接排序并计算最小差值

  • timePoints中的时间字符串HH:MM转成分钟数(0-1400),构建出新的分钟数数组times
  • times进行升序排序
  • times数组末尾推入1440+times[0](最小分钟数),使得最小时间和最大时间的差值计算常规化
  • 对相邻分钟数求差值,最后返回最小差值

整体时间复杂度为O(C)O(C),其中2<=C<=720log7202<=C<=720*log720

代码

var findMinDifference = function (timePoints) {
if (timePoints.length > 1440 || new Set(timePoints).size < timePoints.length) return 0;
if (timePoints.length > 720) return 1;

const times = timePoints.map(p => p.split(':').reduce((a, c, i) => a + (+c) * (i ? 1 : 60), 0));
times.sort((a, b) => a - b).push(1440 + times[0]);

return times.reduce((a, c, i) => i ? Math.min(a, c - times[i - 1]) : a, 1440);
};

2、解法2

执行结果

1.png

解题思路

前置处理:先排除几种特殊情况:timePoints.length > 1440timePoints.length > 720new Set(timePoints).size < timePoints.length

  • timePoints.length > 1440new Set(timePoints).size < timePoints.length,则必定有重复值,最小差值为0
  • timePoints.length > 720最小差值为1。因为分钟数最多1440种情况,如果分钟数量超过一半,则必定有分钟数相邻

后续步骤:把时间字符串转为分钟数,使用计数排序并计算最小差值

  • 创建以分钟数为下标的数组times,将timePoints中的时间字符串HH:MM转成分钟数,对times中的分钟数进行计数
  • 对相邻分钟数求差值,计算出最小差值
  • 计算最小时间和最大时间的差值,并与上一步的最小值比较,最终结果是:Math.min(min, 1440 + minTime - maxTime);

整体时间复杂度为O(C)O(C),其中2<=C<=14402<=C<=1440

代码

var findMinDifference = function (timePoints) {
if (timePoints.length > 1440 || new Set(timePoints).size < timePoints.length) return 0;
if (timePoints.length > 720) return 1;

const times = new Array(1440).fill(0);
for (let p of timePoints) {
times[p.split(':').reduce((a, c, i) => a + (+c) * (i ? 1 : 60), 0)] += 1;
}

let min = 1440, prev = -1440, minTime;
for (let i = 0; i < times.length; i++) {
if (minTime === undefined && times[i]) minTime = i;
if (times[i]) min = Math.min(min, i - prev), prev = i;
}

return Math.min(min, 1440 + minTime - prev);
};

· 阅读需 5 分钟

1、题干

给你一个字符串数组 words ,找出并返回 length(words[i]) * length(words[j]) 的最大值,并且这两个单词不含有公共字母。如果不存在这样的两个单词,返回 0

 

示例 1:

输入:words = ["abcw","baz","foo","bar","xtfn","abcdef"]
输出:16
解释
这两个单词为 "abcw", "xtfn"

示例 2:

输入:words = ["a","ab","abc","d","cd","bcd","abcd"]
输出:4
解释
这两个单词为 "ab", "cd"

示例 3:

输入:words = ["a","aa","aaa","aaaa"]
输出:0
解释
不存在这样的两个单词。

 

提示:

  • 2 <= words.length <= 1000
  • 1 <= words[i].length <= 1000
  • words[i] 仅包含小写字母

思路

  • 暴力解法:

    • 暴力解法是做个嵌套循环遍历所有words,判断两个word是否有相同字母需要再遍历word中的字符
    • 按暴力解法估算时间复杂度大概是O(n^3),n取1000的话,超时基本没跑了
    • 实际上2层嵌套循环基本无法避免,也就是说时间复杂度至少是O(n^2),那就想办法再减少一层遍历
  • 第1层优化-字典:

    • 题目中说到 每个单词只包含小写字母,通常会想到把单词转换成以字母为key的哈希表或者数组字典来做优化
    • 根据题意,使用数组表示单词时,只要将字母对应ASCII码顺序作为索引,值为bool类型即可
    • 举个例子,单词abc可以表示为[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,1],单词a可以表示为[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1]
    • 判断两个单词是否包含相同字母需要做一次遍历,由于数组长度恒定为26,因此时间复杂度为常数
    • 至此,时间复杂度已经可以达到O(n^2)
  • 第2层优化-位运算:

    • 实际上,如果用二进制整数来表示单词会更简洁 abc可以表示为0b111a可以表示为0b1
    • 而且判断两个单词是否有相同字母只需要做一次&运算而不需要遍历数组,&运算结果为0则表示没有相同字母
    • 这个优化不能减少时间复杂度,但是可以减少执行时间
  • 解题步骤:

    • 1、遍历words,将words中的单词转换为长度为2的数组[bit,word.length],数组两个元素分别表示单词的二进制和单词的长度
    • 2、再次遍历words,把当前单词与剩余单词做匹配,如果不存在相同单词,则计算长度乘积
    • 3、返回长度乘积的最大值,具体代码如下
  • 进一步优化:

    • 题解中说到单词转成二进制之后可能会相等,因此可以用哈希表做进一步优化(答题的时候没想到,另外考虑到单词组合问题,26个字母组成1000个单词,最坏情况下转成二进制之后可能没有相等的情况,因此就不写了)
    • 答题的时候发现运算次数达到一定量级之后,位运算的速度比乘方运算更快些(属于自己挖坑了,这里耗时差距能达到10+倍,具体参考附录)

代码

var maxProduct = function (words) {
const CODEA = 'a'.charCodeAt(0);
for (let i = 0; i < words.length; i++) {
let bit = 0;
for (let j = 0; j < words[i].length; j++) {
const p = words[i].charCodeAt(j) - CODEA;
bit = bit | (1 << p);
}
words[i] = [bit, words[i].length];
}

let res = 0;
for (let i = 0; i < words.length; i++) {
for (let j = i + 1; j < words.length; j++) {
if ((words[i][0] & words[j][0]) === 0) {
res = Math.max(res, words[i][1] * words[j][1]);
}
}
}

return res;
};

附录

const K = 1e6;
const MOD = 10;

console.time('乘方');
let n = 0;
for (let i = 0; i < K; i++) {
n = n | (2 ** (i % MOD));
}
console.timeEnd('乘方');

console.time('位运算');
n = 0;
for (let i = 0; i < K; i++) {
n = n | (1 << (i % MOD));
}
console.timeEnd('位运算');
// 浏览器下执行结果
// 乘方: 78.853759765625 ms
// 位运算: 6.093994140625 ms

· 阅读需 1 分钟

1、题干

我们定义,在以下情况时,单词的大写用法是正确的:

  • 全部字母都是大写,比如 "USA"
  • 单词中所有字母都不是大写,比如 "leetcode"
  • 如果单词不只含有一个字母,只有首字母大写, 比如 "Google"

给你一个字符串 word 。如果大写用法正确,返回 true ;否则,返回 false

 

示例 1:

输入:word = "USA"
输出:true

示例 2:

输入:word = "FlaG"
输出:false

 

提示:

  • 1 <= word.length <= 100
  • word 由小写和大写英文字母组成

image.png

解题思路

用正则代码比较简洁

代码

var detectCapitalUse = function (word) {
return /^[a-z]+$|^[A-Z]+$|^[A-Z][a-z]+$/.test(word);
};

· 阅读需 6 分钟

1、题干

给定一个表示代码片段的字符串,你需要实现一个验证器来解析这段代码,并返回它是否合法。合法的代码片段需要遵守以下的所有规则:

  1. 代码必须被合法的闭合标签包围。否则,代码是无效的。
  2. 闭合标签(不一定合法)要严格符合格式:<TAG_NAME>TAG_CONTENT</TAG_NAME>。其中,<TAG_NAME>是起始标签,</TAG_NAME>是结束标签。起始和结束标签中的 TAG_NAME 应当相同。当且仅当 TAG_NAME 和 TAG_CONTENT 都是合法的,闭合标签才是合法的
  3. 合法的 TAG_NAME 仅含有大写字母,长度在范围 [1,9] 之间。否则,该 TAG_NAME 是不合法的
  4. 合法的 TAG_CONTENT 可以包含其他合法的闭合标签cdata (请参考规则7)和任意字符(注意参考规则1)除了不匹配的<、不匹配的起始和结束标签、不匹配的或带有不合法 TAG_NAME 的闭合标签。否则,TAG_CONTENT 是不合法的
  5. 一个起始标签,如果没有具有相同 TAG_NAME 的结束标签与之匹配,是不合法的。反之亦然。不过,你也需要考虑标签嵌套的问题。
  6. 一个<,如果你找不到一个后续的>与之匹配,是不合法的。并且当你找到一个<</时,所有直到下一个>的前的字符,都应当被解析为 TAG_NAME(不一定合法)。
  7. cdata 有如下格式:<![CDATA[CDATA_CONTENT]]>CDATA_CONTENT 的范围被定义成 <![CDATA[ 和后续的第一个 ]]>之间的字符。
  8. CDATA_CONTENT 可以包含任意字符。cdata 的功能是阻止验证器解析CDATA_CONTENT,所以即使其中有一些字符可以被解析为标签(无论合法还是不合法),也应该将它们视为常规字符

合法代码的例子:

输入: "<DIV>This is the first line <![CDATA[<div>]]></DIV>"

输出: True

解释:

代码被包含在了闭合的标签内: <DIV> 和 </DIV> 。

TAG_NAME 是合法的,TAG_CONTENT 包含了一些字符和 cdata 。

即使 CDATA_CONTENT 含有不匹配的起始标签和不合法的 TAG_NAME,它应该被视为普通的文本,而不是标签。

所以 TAG_CONTENT 是合法的,因此代码是合法的。最终返回True。


输入: "<DIV>>> ![cdata[]] <![CDATA[<div>]>]]>]]>>]</DIV>"

输出: True

解释:

我们首先将代码分割为: start_tag|tag_content|end_tag 。

start_tag -> "<DIV>"

end_tag -> "</DIV>"

tag_content 也可被分割为: text1|cdata|text2 。

text1 -> ">> ![cdata[]] "

cdata -> "<![CDATA[<div>]>]]>" ,其中 CDATA_CONTENT 为 "<div>]>"

text2 -> "]]>>]"


start_tag "<DIV>>>" 的原因参照规则 6 。
cdata "<![CDATA[<div>]>]]>]]>" 的原因参照规则 7 。

不合法代码的例子:

输入: "<A>  <B> </A>   </B>"
输出: False
解释: 不合法。如果 "<A>" 是闭合的,那么 "<B>" 一定是不匹配的,反之亦然。

输入: "<DIV> div tag is not closed <DIV>"
输出: False

输入: "<DIV> unmatched < </DIV>"
输出: False

输入: "<DIV> closed tags with invalid tag name <b>123</b> </DIV>"
输出: False

输入: "<DIV> unmatched tags with invalid tag name </1234567890> and <CDATA[[]]> </DIV>"
输出: False

输入: "<DIV> unmatched start tag <B> and unmatched end tag </C> </DIV>"
输出: False

注意:

  1. 为简明起见,你可以假设输入的代码(包括提到的任意字符)只包含数字,字母, '<','>','/','!','[',']'' '

2、解题思路

采用消元思想处理 1、根据 规则1 判断整个字符串是否合法 2、根据其他规则,先消除所有cdata,再循环消除所有标签对 3、若最后结果为空串则说明合法,反之不合法


2022-05-02更新: 这是一年前的写的题解,也是我写的第一个题解。
代码当时可以通过,今天评论区说用例 "<A><B<![CDATA[asd]]>></B></A>" 会失败,也有小哥哥小姐姐给出了改进的代码,非常感谢!
参照大家的方案稍微调整了代码(另外改用了typescript)。问题出在第二行 code = code.replace(/<!\[CDATA\[[\s\S]*?]]>/g, ''); ,只要把第二个参数改成 空格字母数字 等就可以。


问题代码

const isValid = (code) => {
if (!/^<([A-Z]{1,9})>[\s\S]*<\/\1>$/.test(code)) return false;
code = code.replace(/<!\[CDATA\[[\s\S]*?]]>/g, '');
const tagReg = /<([A-Z]{1,9})>[^<]*<\/\1>/g;
while (code && tagReg.test(code)) {
code = code.replace(tagReg, '');
}
return !code;
};

3、改进代码

function isValid(code: string): boolean {
if (!/^<([A-Z]{1,9})>[\s\S]*<\/\1>$/.test(code)) return false;
code = code.replace(/<!\[CDATA\[[\s\S]*?]]>/g, ' ');
const tagReg = /<([A-Z]{1,9})>[^<]*<\/\1>/g;
while (code && tagReg.test(code)) {
code = code.replace(tagReg, '');
}
return !code;
};

4、执行结果

image.png