什么是Anagram
Anagram常被译为变位词,私以为译为易位构词或者易位字谜更准确。变位这个词常跟变格一起作为拉丁语系的语法现象出现,而anagram指将一个单词或词组的字母重新排列,形成另一个具有意义的组合。相同数量和种类的字母、却能产生逻辑相关或相反的单词组合。有点像中文的字谜,只不过象形文字是通过笔画或组成部分的排列组合或增减来变换。
第一次接触到变位词是在某本单词书里,作者写到 dormitory 可以变换成 dirty room,哇哦,魔法!竟然可以这么巧,是人创造了语言还是神创造了语言,巴别塔还有彩蛋吗。
常见的还有:
文艺作品里
Anagram的文字游戏早在希腊就出现了,他们尤其喜欢用来解读名字,相信这里面隐藏着命运的旨意;我想这是很符合希腊哲学的,相信感性的事物后隐藏着普遍的规律。到了中世纪,神学家用它去解读圣经,科学家用它来保护自己的成果(用来记录研究发现避免被抄袭,类似于一种加密手段吧)。而现代的许多小说家、推理作家更是热衷于这个游戏,比如纳博科夫,喜欢以语言为游戏、以文本为迷宫,小说里有大量的双关、变位词、名字暗号、对称结构,之前还看过一篇文章讲他和超文本的渊源。
“正如莎翁《哈姆雷特》是所有复仇题材的原型小说一样,《微暗的火》至今都是文学界、计算机技术界的超文本原型研究主要关注的对象。”
著名的还有:
部分引自 变位词 - 吾萌百科
- 《凉宫春日的分裂》&《凉宫春日的惊愕》
- 渡桥泰水说她的名字读法是わたはし やすみ(Watahashi Yasumi),但实际上正常读作わたはし やすみず(Wa_ta_ha_shi Ya_su_mi_zu)即可,而这个名字的变位是“わたしはすずみや”(Wa_ta_shi wa Su_zu_mi_ya,私は涼宮),意为“我是凉宫”
- 泡坂妻夫
- 日本推理小说家,本名厚川昌男(あつかわ まさお,A_tsu_ka_wa Ma_sa_o),笔名あわさか つまお(A_wa_sa_ka Tsu_ma_o)是其变位词。
- 《爱丽丝镜中奇遇》(刘易斯·卡罗尔)
- 卡罗尔是文字游戏大师。在《Jabberwocky》这首诗中,虽然没有直接的 anagram,但他创造的 nonsense words(无意义词)本身就带有字母重组的趣味性。
- 他本人的名字 Charles Lutwidge Dodgson 的笔名 Lewis Carroll 也是一个 anagram 式的变体(拉丁化双关)。
- 纳博科夫
- 变位词是纳博科夫小说中频繁出现的文字游戏,几乎在其任何一部小说作品中都能找到。…在《洛丽塔》中,他的名字 Vladimir Nabokov 是用变位词的方式隐藏在 Vivian Darkbloom 之中。
- 《美国丽人》作为一部同样以恋童癖为主题的电影,其男主人公莱斯特·伯纳姆(Lester Burnham)的名字致敬了《洛丽塔》:Lester Burnham ⇄ "Humbert learns(亨伯特知错了)"
- 《哈利·波特》系列
- 伏地魔本名汤姆·马沃罗·里德尔(Tom Marvolo Riddle)来源是“我是伏地魔”(I am Lord Voldemort)的变位词。
- 野史记载西弗勒斯·斯内普(Severus Snape)名字来源是"Persues Evans"
- 《达·芬奇密码》
- O, Draconian devil! Oh, lame Saint! (哦,严酷的魔鬼!哦,跛足的圣人!)
⇄ LEONARDO DA VINCI THE MONA LISA(列奥纳多·达·芬奇 《蒙娜丽莎》)
算法题里
anagram相关的算法题比较少且简单,不像回文能衍生出子子孙孙无穷尽也的题目。在忽略词组含义的情况下,说白了就是字母守恒、排列组合。通常给定一个原词,返回给定输入以该词为基础的变位词组合、组合数量等,多用哈希表来记录,可用滑动窗口来优化。
值得一提的是这里哈希表的key有一些trick的做法。比如可以用字母与'a'的UTF-16 编码单元值之差(正好落在1-26)代替字母,如此便可用26长度的数组代替哈希表、数组索引作为key。再比如评论区有人提到,美区有大神用质数表示26个字母,把字符串的各个字母相乘,这样可保证字母异位词的乘积必定是相等的(如果一些质数的乘积相同,那么这些质数一定相同)。当然这个有溢出风险。
49. 字母异位词分组
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
|
/**
* @param {string[]} strs
* @return {string[][]}
*/
var groupAnagrams = function (strs) {
const map = {}
for (let str of strs) {
const wordCnt = Array(26).fill(0)
for (let c of str) {
// str.charCodeAt(index) 是一个用来获取字符串中特定位置字符的 UTF-16编码数值 的方法,不传参时默认index=0
wordCnt[c.charCodeAt() - 'a'.charCodeAt()]++
}
map[wordCnt] ? map[wordCnt].push(str) : map[wordCnt] = [str]
}
return Object.values(map)
};
|
438. 找到字符串中所有字母异位词
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
|
/**
* @param {string} s
* @param {string} p
* @return {number[]}
*/
var findAnagrams = function (s, p) {
const result = []
const sLen = s.length
const pLen = p.length
const sWindowArr = new Array(26).fill(0)
const pWindowArr = new Array(26).fill(0)
const isWindowEqual = (m, n) => {
for (let i = 0; i < 26; i++) {
if (m[i] !== n[i]) {
return false
}
}
return true
}
// 第一个窗口
for (let i = 0; i < pLen; i++) {
sWindowArr[s.charCodeAt(i) - 'a'.charCodeAt(0)]++
pWindowArr[p.charCodeAt(i) - 'a'.charCodeAt(0)]++
}
if (isWindowEqual(sWindowArr, pWindowArr)) {
result.push(0)
}
// 左出右进,定长滑动窗口
for (let i = pLen; i < sLen; i++) {
sWindowArr[s.charCodeAt(i - pLen) - 'a'.charCodeAt(0)]--
sWindowArr[s.charCodeAt(i) - 'a'.charCodeAt(0)]++
if (isWindowEqual(sWindowArr, pWindowArr)) {
result.push(i - pLen + 1)
}
}
return result
};
|
2514. 统计同位异构字符串数目
这个题,纯数学题,涉及一些数论知识,我反手就是抄(不是,抄都懒得抄,看看得了...有空再研究下吧)。
要点有二。一是排列组合运算,对于一个长度为 n 的单词,如果其中字符 c 出现的频次为 cnt[c],那么这个单词的异位词数量为 n! / (cnt[c1]! * cnt[c2]! * ... * cnt[ck]!)。
二是理解模运算和乘法逆元,乘法逆元之于模运算,类似于倒数之于除法运算。除法的分配率在模运算中不成立,因此要用乘法逆元来解决除法的模运算。对于质数 MOD,a 的乘法逆元是 a^(MOD-2) % MOD。(费马小定理 & 欧拉定理 - OI Wiki)