最短单词距离 II

Submission

运行时间: 39 ms

内存: 23.8 MB

class WordDistance:

    def __init__(self, wordsDict: List[str]):
        wordIdx = DefaultDict(int)
        for w in wordsDict:
            if w not in wordIdx:
                wordIdx[w] = len(wordIdx)
        idxs = [[] for _ in range(len(wordIdx))]
        for i, w in enumerate(wordsDict):
            idx = wordIdx[w]
            idxs[idx] .append(i)
        self.wordIdx = wordIdx
        self.idxs = idxs

    def shortest(self, word1: str, word2: str) -> int:
        idxs1, idxs2 = self.idxs[self.wordIdx[word1]
                                 ], self.idxs[self.wordIdx[word2]]
        ans = inf
        i, j = 0, 0
        while i < len(idxs1):
            while j < len(idxs2) and idxs2[j] < idxs1[i]:
                ans = min(ans, idxs1[i] - idxs2[j])
                j += 1
            if j < len(idxs2):
                ans = min(ans, idxs2[j] - idxs1[i])
            else:
                break
            i += 1
        return ans

Explain

该题解采用了预处理+双指针的思路。在初始化时,先用哈希表 wordIdx 将单词映射到对应的序号,然后用列表 idxs 存储每个单词在原数组中出现的所有下标。在计算最短距离时,对于给定的两个单词 word1 和 word2,分别取出它们在 idxs 中存储的下标列表 idxs1 和 idxs2,然后用双指针 i 和 j 同时遍历这两个列表,更新最短距离 ans。

时间复杂度: O(n+m1+m2)

空间复杂度: O(n)

class WordDistance:

    def __init__(self, wordsDict: List[str]):
        # 将单词映射到对应的序号
        wordIdx = DefaultDict(int)
        for w in wordsDict:
            if w not in wordIdx:
                wordIdx[w] = len(wordIdx)
        
        # 存储每个单词在原数组中出现的所有下标
        idxs = [[] for _ in range(len(wordIdx))]
        for i, w in enumerate(wordsDict):
            idx = wordIdx[w]
            idxs[idx].append(i)
        
        self.wordIdx = wordIdx
        self.idxs = idxs

    def shortest(self, word1: str, word2: str) -> int:
        # 取出 word1 和 word2 对应的下标列表
        idxs1, idxs2 = self.idxs[self.wordIdx[word1]], self.idxs[self.wordIdx[word2]]
        ans = inf
        i, j = 0, 0
        
        # 双指针遍历下标列表,更新最短距离
        while i < len(idxs1):
            while j < len(idxs2) and idxs2[j] < idxs1[i]:
                ans = min(ans, idxs1[i] - idxs2[j])
                j += 1
            if j < len(idxs2):
                ans = min(ans, idxs2[j] - idxs1[i])
            else:
                break
            i += 1
        
        return ans

Explore

双指针方法适用于处理两个已排序的列表来寻找最小差值的问题,这种方法可以高效地通过逐步逼近最小差值来减少不必要的比较。对于本题,每个单词的索引列表是有序的,使用双指针可以在线性时间内找到两个列表中索引的最小差距,相比于暴力方法的平方时间复杂度要高效得多。

当`idxs2[j] < idxs1[i]`时,意味着当前`idxs2[j]`的值小于`idxs1[i]`的值,此时增加`j`可以使`idxs2[j]`向`idxs1[i]`靠近,可能得到更小的差值。如果增加`i`,则`idxs1[i]`将远离`idxs2[j]`,从而增大两者之间的差距。因此,为了寻找最小距离,我们应优先考虑增加`j`。

即使当一个单词出现次数远多于另一个单词时,双指针算法仍然是效率较高的方法。这是因为双指针方法只需遍历两个列表一次,总的时间复杂度依然是O(m+n),其中m和n是两个单词的出现次数。双指针算法能够有效地跳过不必要的比较,直接逼近最小距离。

使用`DefaultDict(int)`可以自动处理新单词的索引分配问题。当访问一个尚未存储在字典中的键时,`DefaultDict(int)`会自动为该键创建一个默认值,这里的默认值是从0开始的整数,这样可以自动为新单词分配序号。如果使用普通字典,我们则需要手动检查键是否存在并适当地添加新键和值,这会使代码更加复杂和容易出错。