出处:http://www.fengchang.cc/post/123
leetcode地址:https://leetcode.com/problems/implement-magic-dictionary/
题干我用中文复述一遍。
给定一个单词集合例如{"hello", "world"},和一个单词例如“hhllo",求是否能对给定单词仅做一位变换就能变成给定单词列表中的某个单词。
例如上面的例子对hhllo第二个h变成e就成了hello,hello存在于单词列表(字典)中,所以返回true,如果达不到(例如查找的单词是abccc)就返回false. 这个操作我们定义为search操作。 假设应用场景是需要连续做这样的search很多次,要求尽可能高效,可以用额外的内存空间。
这题乍一看好像不难,直觉上,写一个编辑距离的函数比较两个单词是否能通过一位变换互相达到对方,然后把这个函数应用于所有的单词列表和给定单词对就行了,但是这样显然有一个代价,就是字典有多少个单词每次search就要比对多少个单词,仔细想想有没有必要呢?没有!因为第一,我们只需要比字典中和搜索词长度相等的单词就可以了,长度不等的词根本就没必要比。这是第一个可以优化的方面,再想还有没有其他方面,假设我们已经做到了第一个优化方面,在常数时间内过滤出了所有长度相等的单词,第二步该怎么做呢?
画了个图找灵感:
我们把单词按行堆叠如图,会有一种按列搜索的灵感直觉(所以说画图很重要),发现如果我们要查abc,那么假如逐位查找index,那么第一列中我们找哪些行有a,得到【1,2,4,5】行,第二列中哪些行有b,得到【0,1】,第三列哪些行有c,得到【0,2】,如果我们得到这三个行下标数组,不就很容易得出是否有能通过一位变换达到的行吗?例如前三行,每行都可以找到两个相同的位置,而单词长度为3,那么就只有1位匹配不上,即通过一次单词变换可达。
这就是最终要的直觉思路。
整个思路再理一遍就是,因为可以利用额外的内存空间,我们可以自己定义一个数据结构来初始化字典,而非用一个简单的单词数组或者类似的结构,比如如果我们用python的dict(相当于java的map)数据结构,Key为单词长度,value为单词列表,那么当我们search一个单词的时候,就可以根据这个单词的长度在常数时间内缩小到符合这个长度的字典的子集。随后针对这个字典子集,我们想办法按列存储,使得对每列,我搜一个字母,就能确定哪些行包含这个字母,由此想到仍然可以用一个dict数据结构来实现,针对单词长度的每位我们collect一个dict出来,key为字母,value为其出现的行号。最后再对得到的搜索词的每一位按列搜索出来的行号做比对,collect出每一行的位匹配数,确定哪些行匹配数目恰好等于单词长度减1,即找到匹配行,如果找不到这样的匹配行,则返回false.
以上是基本思路。要注意一个边界条件,就是我们得到【1,2,4,5】, 【0,1】,【0,2】这样的index时,实际上代表着和我们知道0,1,2,4,5行是备选行,但是很明显这里还可能漏掉一些行没关注,就是那些一个位都没match上的行,为什么我们还要关注那些漏掉的行?比如说行号为3的行,这个行代表的含义是什么呢?代表完全match不上,这通常不会是什么问题,但是假如搜索单词长度为1时,那么'a'和'b'也被认为是完全match不上,但是因为只有一位,所以也是符合转换条件的,所以我们对边界条件的处理是把那些没关注的行业全部置match数为0,最终比单词长度跟match数的时候,所有行统一比。所以边界条件是需要这些collect时漏掉的行match数全部置0.
下面是实现代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 |
|