昨天是元宵节,leetcode的每日一题也很应景出了一道猜字谜的题目。
既然真正的猜字谜我不擅长,还是来看看这道算法的猜字谜吧。
首先上题目的链接:
https://leetcode-cn.com/problems/number-of-valid-words-for-each-puzzle/
解题思路我们可以设计出解决该字谜问题的一个算法流程:
有了这个方案我们就可以思考如何设计这个数据结构,让我们能快速的存储和查找。
二进制状态压缩 状态压缩由于题目中规定word 和puzzle 均只包含小写字母,因此 Sw和 Sp的大小最多为 26,我们可以考虑使用一个长度为 26的二进制数 bw或 bp 来表示这一集合。
为什么叫状态压缩呢?其实这个数据结构我们也可以用一个长度为26的数组来表示,但是这样就需要26个Number的空间,但是如果我们用一个Number的二进制形式来表示26个字母是否出现,就可以省下26倍的空间。
因此我们可以使用一个哈希映射来表示需要的「数据结构」:对于哈希映射中的每一个键值对,其中的键表示一个长度为 26 的二进制数,值表示其出现的次数,即数组 words 中多少个 word 压缩成的二进制数等于键。构造哈希映射的过程也很简单:我们只需要遍历每一个 word,并遍历 word 中的每一个字母,将对应位置的二进制位标记为 1,这样就计算出了word 对应的二进制表示,将其在哈希映射中作为键对应的值增加 1 即可。
代码如下:
1 2 3 4 5 6 7 8 9 10 11 const freq = new Map ();for (const word of words) { let mask = 0 ; for (const ch of word) { mask |= (1 << (ch.charCodeAt () - 'a' .charCodeAt ())); } if (CountOne (mask) <= 7 ) { freq.set (mask, (freq.get (mask) || 0 ) + 1 ); } }
每次循环中的mask就是压缩后的二进制数,CountOne的作用是返回mask转换为二进制后1的数量
因为每个puzzle长度都为7,所以只有mask中1的数量不超过7的时候才有可能有答案。
二进制遍历接下来我们要对每个puzzle也进行二进制的状态压缩,计算每个puzzle的状态,然后遍历每个状态的子集,看看freq中有几个相同的。
枚举二进制的伪代码1 2 3 4 5 6 7 8 9 function get_subset (bitmask ) subset = bitmask answer = [bitmask] while subset != 0 subset = (subset - 1 ) & bitmask put subset into the answer list end while return answer end function
代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 for (const puzzle of puzzles) { let total = 0 ; let mask = 0 ; for (let i = 1 ; i < 7 ; i++) { mask |= (1 << (puzzle[i].charCodeAt () - 'a' .charCodeAt ())); } let subset = mask; while (subset) { let s = subset | (1 << (puzzle[0 ].charCodeAt () - 'a' .charCodeAt ())); if (freq.has (s)) { total += freq.get (s); } subset = (subset - 1 ) & mask; } if (freq.has (1 << (puzzle[0 ].charCodeAt () - 'a' .charCodeAt ()))) { total += freq.get (1 << (puzzle[0 ].charCodeAt () - 'a' .charCodeAt ())); } ans.push (total); }
完整代码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 function CountOne (n ) { const str = n.toString (2 ); let count = 0 ; for (const ch of str) { if (parseInt (ch) === 1 ) { count++; } } return count; } var findNumOfValidWords = function (words, puzzles ) { const freq = new Map (); for (const word of words) { let mask = 0 ; for (const ch of word) { mask |= (1 << (ch.charCodeAt () - 'a' .charCodeAt ())); } if (CountOne (mask) <= 7 ) { freq.set (mask, (freq.get (mask) || 0 ) + 1 ); } } const ans = []; for (const puzzle of puzzles) { let total = 0 ; let mask = 0 ; for (let i = 1 ; i < 7 ; i++) { mask |= (1 << (puzzle[i].charCodeAt () - 'a' .charCodeAt ())); } let subset = mask; while (subset) { let s = subset | (1 << (puzzle[0 ].charCodeAt () - 'a' .charCodeAt ())); if (freq.has (s)) { total += freq.get (s); } subset = (subset - 1 ) & mask; } if (freq.has (1 << (puzzle[0 ].charCodeAt () - 'a' .charCodeAt ()))) { total += freq.get (1 << (puzzle[0 ].charCodeAt () - 'a' .charCodeAt ())); } ans.push (total); } return ans; };