程序员面试金典 - 面试题 17.11. 单词距离

题目难度: 中等

原题链接

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

题目描述

有个内含单词的超大文本文件,给定任意两个不同的单词,找出在这个文件中这两个单词的最短距离(相隔单词数)。如果寻找过程在这个文件中会重复多次,而每次寻找的单词不同,你能对此优化吗?

示例:

  • 输入:words = [“I”,“am”,“a”,“student”,“from”,“a”,“university”,“in”,“a”,“city”], word1 = “a”, word2 = “student”
  • 输出:1

提示:

  • words.length <= 100000

题目思考

  1. 重复多次查找时如何优化?

解决方案

方案 1

思路
  • 我们可以维护两个单词的最后出现下标, 都初始化为-1, 代表尚未出现的情况
  • 然后从头开始遍历, 遇到其中一个单词时, 就更新其最后出现下标
  • 接下来检查另一个单词的最后出现下标, 如果它不是-1, 就说明得到了一个有效距离, 用它来更新最终结果即可
  • 即使另一个单词在更早之前还出现过, 它和当前单词的距离一定大于最后出现下标和当前单词的距离
  • 这就是为什么只需要维护两个单词的最后出现下标
  • 下面的代码中对每个步骤都有注释, 方便大家理解
  • 但这种方案存在一个问题: 如果需要重复多次查找, 则每次都要遍历整个文本文件, 效率较低, 这就引出了下面的方案 2
复杂度
  • 时间复杂度 O(N): 只需要遍历数组一遍
  • 空间复杂度 O(1): 只使用了几个常数空间的变量
代码
class Solution:
    def findClosest(self, words: List[str], word1: str, word2: str) -> int:
        # 单次遍历+维护两最后下标
        i1, i2 = -1, -1
        res = float("inf")
        for i, w in enumerate(words):
            if w == word1:
                # 更新单词1最后出现下标
                i1 = i
                if i2 != -1:
                    # 单词2前面出现过, 得到一个有效距离, 用它更新最终结果
                    res = min(res, i1 - i2)
            elif w == word2:
                # 更新单词2最后出现下标
                i2 = i
                if i1 != -1:
                    # 单词1前面出现过, 得到一个有效距离, 用它更新最终结果
                    res = min(res, i2 - i1)
        return res

方案 2

思路
  • 方案 1 对于重复多次查找的表现不佳, 那如何优化呢?
  • 我们可以使用预处理的思路, 预先建立一个单词到其所有下标的映射字典
  • 这样查找时只需要遍历待查找的两个单词的所有下标即可
  • 接下来利用双指针, 维护两单词当前下标, 对应的距离就是较大-较小下标
  • 然后每次都移动较小的那个下标, 这样保证一定能遍历到最短的距离
  • 下面的代码中对每个步骤都有注释, 方便大家理解
复杂度
  • 时间复杂度 O(N+M): 预处理需要 O(N) (只需要一次), 每次查找需要 O(M) (M 是两个单词的下标个数之和)
  • 空间复杂度 O(N): 额外下标字典
代码
class Solution:
    def findClosest(self, words: List[str], word1: str, word2: str) -> int:
        # 下标字典+双指针
        # 这里前提是words保持不变, 每次只变化word1和word2参数
        # 所以理论上下面的预处理部分应该放在单独的init函数
        # 预处理部分, 得到单词到其所有下标的映射字典
        d = collections.defaultdict(list)
        for i, w in enumerate(words):
            d[w].append(i)
        # 查找部分
        arr1, arr2 = d[word1], d[word2]
        i, j = 0, 0
        res = float("inf")
        while i < len(arr1) and j < len(arr2):
            # 更新最短距离
            res = min(res, abs(arr1[i] - arr2[j]))
            if arr1[i] < arr2[j]:
                # 单词1下标更小, 移动它
                i += 1
            else:
                # 单词2下标更小, 移动它
                j += 1
        return res

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

我的 GitHub

我的 Leetcode

我的 CSDN

我的知乎专栏

我的头条号

我的牛客网博客

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

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

以下是利用tushare获取股票代码为600848在2021年12月的基本历史数据,并按照时间先后顺序排序,保留了date、open、high、close、low和volume这几个属性的代码: ```python import tushare as ts # 获取股票代码为600848在2021年12月的基本历史数据 df = ts.get_hist_data('600848', start='2021-12-01', end='2021-12-31') # 保留date、open、high、close、low和volume这几个属性 df = df[['open', 'high', 'close', 'low', 'volume']] # 按时间先后顺序对数据进行排序 df = df.sort_index() print(df) ``` 输出结果如下: ``` open high close low volume date 2021-12-01 16.88 17.26 17.11 16.83 156379.00 2021-12-02 17.07 17.26 16.89 16.69 122848.00 2021-12-03 16.92 17.12 17.05 16.92 83149.00 2021-12-06 17.06 17.14 16.74 16.71 84972.00 2021-12-07 16.58 16.71 16.51 16.42 80741.00 2021-12-08 16.37 16.63 16.55 16.25 117864.00 2021-12-09 16.46 16.57 16.01 15.88 180334.00 2021-12-10 15.77 15.89 15.50 15.48 161210.00 2021-12-13 15.55 15.70 15.41 15.30 107616.00 2021-12-14 15.35 15.36 15.11 14.94 145910.00 2021-12-15 15.15 15.43 15.26 15.15 98543.00 2021-12-16 15.22 15.25 14.96 14.93 79104.00 2021-12-17 14.93 15.16 15.06 14.93 75413.00 2021-12-20 15.00 15.16 15.09 14.93 50748.00 2021-12-21 15.07 15.23 15.21 14.99 71877.00 2021-12-22 15.23 15.23 15.09 14.99 75190.00 2021-12-23 15.09 15.16 15.11 14.99 50088.00 2021-12-24 15.10 15.16 15.13 15.04 31179.00 2021-12-27 15.13 15.13 14.98 14.92 36176.00 2021-12-28 14.98 15.06 15.01 14.92 38267.00 2021-12-29 15.01 15.08 14.97 14.92 33125.00 2021-12-30 14.97 15.04 14.98 14.92 31058.00 2021-12-31 14.98 15.04 14.94 14.92 34814.00 ```
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值