前端刷题路-Day99:通过删除字母匹配到字典里最长单词(题号524)

685 阅读3分钟

通过删除字母匹配到字典里最长单词(题号524)

题目

给你一个字符串 s 和一个字符串数组 dictionary 作为字典,找出并返回字典中最长的字符串,该字符串可以通过删除 s 中的某些字符得到。

如果答案不止一个,返回长度最长且字典序最小的字符串。如果答案不存在,则返回空字符串。

示例 1:

输入:s = "abpcplea", dictionary = ["ale","apple","monkey","plea"]
输出:"apple"

示例 2:

输入:s = "abpcplea", dictionary = ["a","b","c"]
输出:"a"

提示:

  • 1 <= s.length <= 1000
  • 1 <= dictionary.length <= 1000
  • 1 <= dictionary[i].length <= 1000
  • sdictionary[i] 仅由小写英文字母组成

链接

leetcode-cn.com/problems/lo…

解释

这题啊,这题是人类迷惑行为

题目很容易让人误解,第一次提交就给笔者整不会了

首先看看题目,整体来说比较简单,就是比较ab两个字符串,看看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函数上,新建ij两个指针用来表示当前走到了字符串的哪个位置,别的基本上就没了。

可就这简单的一小步,让性能从5%到了80%,不管是内存占用还是运行时间。

其它的思路

注意,这里并不是更好的方法,而是其它思路。

因为笔者认为这两个方案并不比👆的双指针好,感觉有点多此一举。

  1. 排序

    排序是给什么排序?给dictionary排序。

    排序是为了什么?为了不用后续再进行判断。

    怎么排序?将 \textit{dictionary}dictionary 依据字符串长度的降序和字典序的升序进行排序,然后从前向后找到第一个符合条件的字符串直接返回即可。

  2. DP

    DP就更离谱了,笔者懒得说了,具体可以参照官方答案👉:这里



PS:想查看往期文章和题目可以点击下面的链接:

这里是按照日期分类的👇

前端刷题路-目录(日期分类)

经过有些朋友的提醒,感觉也应该按照题型分类
这里是按照题型分类的👇

前端刷题路-目录(题型分类)