程序员面试金典 - 面试题 17.17. 多次搜索

题目难度: 中等

原题链接

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

题目描述

给定一个较长字符串 big 和一个包含较短字符串的数组 smalls,设计一个方法,根据 smalls 中的每一个较短字符串,对 big 进行搜索。输出 smalls 中的字符串在 big 里出现的所有位置 positions,其中 positions[i]为 smalls[i]出现的所有位置。

示例:

  • 输入:
    • big = “mississippi”
    • smalls = [“is”,“ppi”,“hi”,“sis”,“i”,“ssippi”]
  • 输出:
    • [[1,4],[8],[],[3],[1,4,7,10],[5]]

提示:

  • 0 <= len(big) <= 1000
  • 0 <= len(smalls[i]) <= 1000
  • smalls 的总字符数不会超过 100000。
  • 你可以认为 smalls 中没有重复字符串。
  • 所有出现的字符均为英文小写字母。

题目思考

  1. 如何尽可能优化时间复杂度?

解决方案

方案 1

思路
  • 分析题目, 一个很容易想到的方案就是使用各个语言提供的字符串 find()函数
  • 然后针对每个 small 字符串, 查找它在 big 字符串中所有可能的起点
  • 以 Python3 为例, find()可以传入待查找的起点, 并返回从那个起点开始查找到的子串的首个起点, 如果没找到则返回-1
  • 这样我们就可以不停循环上述过程, 直到找不到 small 子串为止
  • 下面代码有详细的注释, 方便大家理解
复杂度
  • 时间复杂度 O(NMS): 假设 N 是 big 字符串的长度, M 是 smalls 字符串数组的元素个数, S 是 small 字符串的平均长度; 则外层循环复杂度为 O(M), 内层循环最多需要对每个起点匹配对应的子串, 就是 O(NS), 所以总时间复杂度是 O(NMS)
  • 空间复杂度 O(1): 除了结果数组外, 没有使用额外的空间
代码
class Solution:
    def multiSearch(self, big: str, smalls: List[str]) -> List[List[int]]:
        # 方法1: 语言自带find
        res = []
        for s in smalls:
            res.append([])
            # 初始化起点为-1, 因为下面的find逻辑是从i+1开始查找
            i = -1
            if s != "":
                while True:
                    # i是上一个查找到的起点, 接下来从i+1开始查, 保证不重复
                    i = big.find(s, i + 1)
                    if i != -1:
                        # 找到了, 追加起点
                        res[-1].append(i)
                    else:
                        # 没找到, 跳出循环
                        break
        return res

方案 2

思路
  • 回顾方案 1, 可以发现它在查找起点时会进行很多重复的匹配, 效率比较低, 有没有更优的方案呢?
  • 重新分析题目, 我们可以发现, 某个 small 字符串要想成为 big 的子串, 则它一定是 big 从某个起点开始的前缀
  • 所以这里我们可以引入前缀树 trie, 将所有 small 字符串加入 trie 中
  • 然后依次遍历 big 的每个起点, 查找从它开始的某个前缀是否已经在 trie 中, 是的话则说明该前缀就是一个 small 字符串
  • 这里我们需要对每个节点额外加上一个属性 index, 来代表当前 small 字符串的下标
  • 这样在找到一个前缀后, 我们就可以对结果列表对应的下标处追加上当前的 big 起点了
  • 下面代码有详细的注释, 方便大家理解
复杂度
  • 时间复杂度 O(MS+NS): 假设 N 是 big 字符串的长度, M 是 smalls 字符串数组的元素个数, S 是 small 字符串的平均长度; 则插入 trie 的复杂度为 O(MS), 查找起点时, 外层循环复杂度为 O(N), 内层最多遍历 S 层, 复杂度为 O(S), 所以这部分总复杂度为 O(NS)
  • 空间复杂度 O(MS): 需要存储 small 字符串列表的所有字符
代码
class Solution:
    def multiSearch(self, big: str, smalls: List[str]) -> List[List[int]]:
        # 方法2: 逆向思考+trie存small+遍历big起点
        class Node:
            def __init__(self, c):
                self.c = c
                # children字典, 存储字符到节点的映射
                self.children = {}
                # small字符串下标, 初始化为-1, 只有当遍历到small最后一个字符时才赋值, 保证完整性
                self.index = -1

        n, m = len(big), len(smalls)
        root = Node("")
        for i, w in enumerate(smalls):
            # 将当前字符串加入trie中, 达到终点时记录当前下标
            # 因为smalls的字符串都不重复, 所以不存在下标覆盖的情况
            cur = root
            for c in w:
                if c not in cur.children:
                    cur.children[c] = Node(c)
                cur = cur.children[c]
            # 赋值下标, 从root到cur的路径形成了完整的当前small字符串
            cur.index = i
        # 预生成结果列表
        res = [[] for _ in range(m)]
        for start in range(n):
            # 遍历big的每个起点start
            cur = root
            i = start
            while cur and i < n:
                c = big[i]
                if c not in cur.children:
                    # 当前字符不在trie中, 后面就更不存在有效的small字符串了, 直接退出循环
                    break
                # 进入下一层
                cur = cur.children[c]
                i += 1
                if cur.index != -1:
                    # 当前节点索引不是-1, 说明找到了一个完整的small字符串 (从root一路到cur)
                    # 此时需要将结果列表的small下标处追加上当前big的起点start, 表示这个small字符串在start位置处出现
                    res[cur.index].append(start)
        return res

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

我的 GitHub

我的 Leetcode

我的 CSDN

我的知乎专栏

我的头条号

我的牛客网博客

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

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

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值