题目难度: 中等

原题链接

今天继续更新程序员面试金典系列, 大家在公众号 算法精选 里回复 面试金典 就能看到该系列当前连载的所有文章了, 记得关注哦~

题目描述

给定一组单词 words,编写一个程序,找出其中的最长单词,且该单词由这组单词中的其他单词组合而成。若有多个长度相同的结果,返回其中字典序最小的一项,若没有单词则返回空字符串。

示例:

  • 输入: ["cat","banana","dog","nana","walk","walker","dogwalker"]
  • 输出: "dogwalker"
  • 解释: "dogwalker"可由"dog"和"walker"组成。

提示:

  • 0 <= len(words) <= 200
  • 1 <= len(words[i]) <= 100

题目思考

  1. 如何判断单词是否由其他单词组合而成?

解决方案

思路

  • 分析题目, 要想求得符合要求的最长单词, 我们可以先按照题目给定的规则对单词排序 (长度降序, 相等时按照字典序升序), 这样第一个满足要求的单词就是最长单词, 无需继续检查
  • 那如何检查某个单词是否是其他单词的组合呢?
  • 对于当前待检查单词, 如果它的某个前缀是某个输入单词, 那么只需要保证剩余部分也是某个输入单词, 或者它也是若干单词的组合即可
  • 所以这里我们可以采用记忆化搜索的思想, 引入两个参数: 当前待检查单词 w, 是否允许整体是输入单词 allowWhole
  • 在检查某个单词是否是其他单词的组合时, 传入 allowWhole 是 False, 表示至少需要是其他两个单词的组合
  • 然后遍历其每个分隔, 如果前缀是某个输入单词, 则只需要递归检查剩余部分, 此时传入的 allowWhole 就是 True 了, 表示剩余部分可以单独作为一个单词使用, 如果它本身就是某个输入单词, 那直接返回 True 即可
  • 另外在判断某个单词是否是某个输入单词时, 我们可以将给定的单词列表转成集合, 这样能加速查找效率
  • 还有可以将判断函数缓存起来 (@functools.cache), 避免重复计算, 这就是典型的记忆化的思想
  • 下面代码有详细的注释, 方便大家理解

复杂度

  • 时间复杂度 O(NlogN+NC): 假设字符串数目是 N, 平均长度是 C, 那么排序复杂度为 O(NlogN), 记忆化搜索的复杂度为 O(NC)
  • 空间复杂度 O(N): 单词集合和记忆化搜索占用的空间都是 O(N)

代码

class Solution:
    def longestWord(self, words: List[str]) -> str:
        # 先将单词列表转成集合, 加速查找效率
        ws = set(words)
        # 然后将单词按照长度降序排序, 长度相等时按照字典序升序排序
        # 这样第一个满足要求的单词就是最长单词
        words.sort(key=lambda w: (-len(w), w))

        @functools.cache
        def isValid(w, allowWhole):
            # allowWhole表示是否允许w作为单独单词出现在集合中
            # 值为False时则必须由至少两个出现在集合中的单词组合而成
            if allowWhole and w in ws:
                return True
            for i in range(1, len(w)):
                # 检查每种组合的可能性
                if w[:i] in ws and isValid(w[i:], True):
                    return True
            return False

        for w in words:
            if isValid(w, False):
                # 当前单词可由其他单词组合得到, 为有效的最长单词, 直接返回
                return w
        # 没找到有效组合单词
        return ""

大家可以在下面这些地方找到我~😊

我的 GitHub

我的 Leetcode

我的 CSDN

我的知乎专栏

我的头条号

我的牛客网博客

我的公众号: 算法精选, 欢迎大家扫码关注~😊

算法精选 - 微信扫一扫关注我