通过删除字母匹配到字典里最长单词(题号524)
题目
给你一个字符串 s 和一个字符串数组 dictionary 作为字典,找出并返回字典中最长的字符串,该字符串可以通过删除 s 中的某些字符得到。
如果答案不止一个,返回长度最长且字典序最小的字符串。如果答案不存在,则返回空字符串。
示例 1:
输入:s = "abpcplea", dictionary = ["ale","apple","monkey","plea"]
输出:"apple"
示例 2:
输入:s = "abpcplea", dictionary = ["a","b","c"]
输出:"a"
提示:
1 <= s.length <= 10001 <= dictionary.length <= 10001 <= dictionary[i].length <= 1000s和dictionary[i]仅由小写英文字母组成
链接
解释
这题啊,这题是人类迷惑行为
题目很容易让人误解,第一次提交就给笔者整不会了
首先看看题目,整体来说比较简单,就是比较a,b两个字符串,看看b是不是在a里面,a中有多余的也没关系,去掉就好了,这都没啥。
很简单就能写一个对数组中元素的比较,从头开始,如果两个数组的第一个元素相同,那就将相同元素去掉;如果不同,只去掉a数组的第一个元素。
就这样一直循环到数组某一个数组为空,此时判断b的长度是否为空,如果为空则证明这个字符串是正确的,可以和最后的结果进行对比了,否则GG。
和最后的结果对比才是最恶心的,注意题目中的这句话:
返回长度最长且字典序最小的字符串
这里的关键点是字典序,正常人看到这题的第一反应就是字符串在dictionary中的顺序吧,要保持这个顺序其实很简单,如果当前字符串的长度等于结果字符串的长度,不做任何处理即可;只有当当前字符串的长度比结果字符串更长的时候,才进行替换操作。
正常人都是这么想的,对吧
可题目的真正的意思是字符串的字典序,给我都看懵了,因为笔者遇到这了这样一个测试用例:
"abce"
["abe","abc"]
按照最开始的思路,答案显然是abe,结果却是abc。
那么很明显这里的字典序是字符串中字母的字典序了,而不是在dictionary中的顺序。
要判断这个其实不难,直接用大于号或者小于号比较即可,可重点是能不能理解这里字典序的意思。
回到解法中,之前说过,可以一直比较数组的第一个元素,然后不断去掉数组的第一个元素来进行比较。这样其实是比较耗性能的,毕竟多了一步字符串转数组和shift操作。那有没有更好的办法呢?显然是有的。
双指针可以更简单的进行判断,两个指针从0开始,不断比较两个字符串中指针位置上的字母是否相同,如果相同就移动两个指针,如果不同只移动a指针。
最后只需要比较b指针是否和b字符串长度相等即可,算是简化了不少。
这题官方还提供了另外两种解法,但笔者认为这就是为了有多种解法而提供的解法,这里就不详细介绍了,👇会错略带过。
自己的答案(数组)
经典解法,利用数组来判断两个字符串的关系。
var findLongestWord = function(s, dictionary) {
function checkStr(a, b) {
while (a.length && b.length) {
if (a[0] === b[0]) b.shift()
a.shift()
}
return !b.length
}
let res = ''
for (const word of dictionary) {
if (checkStr(s.split(''), word.split(''))) {
res = word.length > res.length || (word.length === res.length && word < res) ? word : res
}
}
return res
};
比较的时候会将两个字符串转化成两个数组,一直比较数组的头部元素,暨此判断字符串的最终状态。
之后和结果进行对应的比较,一直更新即可拿到最后的答案。
自己的答案(双指针)
双指针优化的主要是两个字符串比较的过程,利用双指针来避免数组的相关操作,省去了一些耗时的操作。
var findLongestWord = function(s, dictionary) {
function checkStr(a, b) {
let i = 0, j = 0
while (i < a.length && j < b.length) {
if (a[i] === b[j]) j++
i++
}
return j === b.length
}
let res = ''
for (const word of dictionary) {
if (checkStr(s, word)) {
res = word.length > res.length || (word.length === res.length && word < res) ? word : res
}
}
return res
};
可以看出,代码的变化主要在于checkStr函数上,新建i和j两个指针用来表示当前走到了字符串的哪个位置,别的基本上就没了。
可就这简单的一小步,让性能从5%到了80%,不管是内存占用还是运行时间。
其它的思路
注意,这里并不是更好的方法,而是其它思路。
因为笔者认为这两个方案并不比👆的双指针好,感觉有点多此一举。
-
排序
排序是给什么排序?给
dictionary排序。排序是为了什么?为了不用后续再进行判断。
怎么排序?将 \textit{dictionary}dictionary 依据字符串长度的降序和字典序的升序进行排序,然后从前向后找到第一个符合条件的字符串直接返回即可。
-
DP
DP就更离谱了,笔者懒得说了,具体可以参照官方答案👉:这里。
PS:想查看往期文章和题目可以点击下面的链接:
这里是按照日期分类的👇
经过有些朋友的提醒,感觉也应该按照题型分类
这里是按照题型分类的👇