这一类题目通常需要一些辅助标记,通常是用到HashMap()
或者是二进制的mask
。
面试题 17.11. 单词距离
有个内含单词的超大文本文件,给定任意两个不同的单词,找出在这个文件中这两个单词的最短距离(相隔单词数)。如果寻找过程在这个文件中会重复多次,而每次寻找的单词不同,你能对此优化吗?
示例:
输入:words = [“I”,“am”,“a”,“student”,“from”,“a”,“university”,“in”,“a”,“city”], word1 = “a”, word2 = “student”
输出:1
虽然题目中的单词会重复,但其实与当前 w o r d [ i ] = = w o r d 2 word[i] ==word2 word[i]==word2 距离最近的 w o r d 1 word1 word1 只有两种情况,即遍历中最新的一个 w o r d [ l a s t 1 ] = = w o r d 1 , l a s t 1 < i word[last1] == word1, last1< i word[last1]==word1,last1<i 或者是下一个 w o r d [ n e x t 1 ] = = w o r d 1 , n e x t 1 > i word[next1] == word1, next1> i word[next1]==word1,next1>i。但其实后一种情况会在遍历到 w o r d [ i ′ ] = = w o r d 1 , i ′ = n e x t 1 word[i'] == word1, i' = next1 word[i′]==word1,i′=next1时,通过计算 w o r d [ l a s t 1 ′ ] = = w o r d 2 , l a s t 1 ′ = i word[last1'] == word2, last1' = i word[last1′]==word2,last1′=i得到。因此,每次其实只需要考虑前一种情况就可以。即当 w o r d [ i ] = = w o r d 1 ( w o r d 2 ) word[i] == word1(word2) word[i]==word1(word2)时,比较 a n s ans ans与 i − l a s t 2 ( l a s t 1 ) i - last2(last1) i−last2(last1)的大小,并更新 l a s t 1 ( l a s t 2 ) = i last1(last2)=i last1(last2)=i即可。
class Solution:
def findClosest(self, words: List[str], word1: str, word2: str) -> int:
last1 = last2 = -1
ans = 100000
for i in range(len(words)):
if words[i] == word1:
if cur_2 != -1:
ans = min(ans, i-last2)
cur_1 = i
elif words[i] == word2:
if cur_1 != -1:
ans = min(ans, i-last1)
cur_2 = i
return ans
若是需要
k
k
k次查找,那么如果用上述方法复杂度将是
O
(
k
n
)
O(kn)
O(kn)。此类题目往往可以用空间换时间。因为要对比的其实是某两个单词在数组中出现的位置,因此可以用一个Map()
来记录每个单词在数组中出现的位置,之后查找只需要使用对应词的下标数组就可以得到结果。
拓展
318. 最大单词长度乘积
给你一个字符串数组 words ,找出并返回 length(words[i]) * length(words[j]) 的最大值,并且这两个单词不含有公共字母。如果不存在这样的两个单词,返回 0 。
示例 1:
输入:words = [“abcw”,“baz”,“foo”,“bar”,“xtfn”,“abcdef”]
输出:16
解释:这两个单词为 “abcw”, “xtfn”。
示例 2:
输入:words = [“a”,“ab”,“abc”,“d”,“cd”,“bcd”,“abcd”]
输出:4
解释:这两个单词为 “ab”, “cd”。
示例 3:
输入:words = [“a”,“aa”,“aaa”,“aaaa”]
输出:0
解释:不存在这样的两个单词。
暴力解法是先计算两个字符串间是否有重复字符,然后再计算乘积的值。而基础的判断是否重复需要将两个字符串间的每个字符进行比较,时间复杂度比较高。判断重复很多时候能用到HashMap()
,但在这里,因为字母个数26<32,也就是一个int类型的数据大小,那么就可以用更简单的判断是否重复的方法:位运算。也就是用一个26位的二进制来表示,其中为1的表明该字符串含有某个字母。这样,如果两个字符串没重复的数,那么二者&
后的结果应该是0。这一类字母或者有限数字的题,都可以考虑一下用这种方法来判断重复,但有一个前提是,不需要知道 顺序,也就是不能用这种方法来进行类似滑动窗口的操作,因为你不知道滑动窗口左侧的数消失时,mask
中应该去掉的是哪一位。
class Solution:
def maxProduct(self, words: List[str]) -> int:
def mask(word):
return sum(1 << (ord(c) - ord('a')) for c in set(word))
#将对应字符串转为二进制mask
maxx = 0
for i in range(len(words)):
for j in range(i+1, len(words)):
if not mask(words[j]) & mask(words[i]): #两个字符串没有重复的字母
maxx = max(len(words[i]) * len(words[j]), maxx)
return maxx
当然,这题很明显还能优化,比如每个字符串只需要mask()
一次并记录,而不需要每次都转换一次;当两个字符串的mask()
值相同,如meet和met时,只需要用长度长的就可以。总之,这种需要判断的属性个数有限时,可以想到用二进制掩码来做。