词排名部分完成
我不太确定如何在这些限制条件下解决这个问题。
简化的问题描述:
- 把“单词”看作是任何由大写字母A-Z组成的序列(不仅限于“字典单词”)。
- 考虑一个单词中所有字符的排列组合,并按字母顺序排序。
- 找出原始单词在这个列表中的位置。
- 不要生成所有可能的单词排列,因为这样会超出时间和内存的限制。
- 限制条件:单词长度小于等于25个字符;内存限制为1GB,任何答案都应该适合64位整数。
原始问题描述:
把“单词”看作是任何由大写字母A-Z组成的序列(不仅限于“字典单词”)。对于任何至少有两个不同字母的单词,都会有其他由相同字母但顺序不同的单词(例如,STATIONARILY和ANTIROYALIST,它们都是字典中的单词;在这里,“AAIILNORSTTY”也是由这两个单词的字母组成的“单词”)。我们可以根据单词在按字母顺序排列的所有由相同字母组成的单词列表中的位置,为每个单词分配一个编号。一个方法是生成整个单词列表,然后找到所需的单词,但如果单词很长,这样会很慢。编写一个程序,接受一个单词作为命令行参数,并将其编号打印到标准输出。不要使用上述生成整个列表的方法。你的程序应该能够接受任何长度不超过25个字母的单词(可能有些字母重复),并且使用的内存不超过1GB,运行时间不超过500毫秒。我们检查的任何答案都应该适合64位整数。
示例单词及其排名:
ABAB = 2
AAAB = 1
BAAA = 4
QUESTION = 24572
BOOKKEEPER = 10743
示例:
AAAB - 1
AABA - 2
ABAA - 3
BAAA - 4
AABB - 1
ABAB - 2
ABBA - 3
BAAB - 4
BABA - 5
BBAA - 6
我想出的解决方案似乎只是部分解决。
假设我有一个单词 JACBZPUC
。我对这个单词进行排序,得到 ABCCJPUZ
。这个单词在排名中应该是第一名。从 ABCCJPUZ
到以 J
开头的第一个字母的单词之间,我想找出这两个单词之间的排列数量。
例如:
for `JACBZPUC`
sorted --> `ABCCJPUZ`
permutations that start with A -> 8!/2!
permutations that start with B -> 8!/2!
permutations that start with C -> 8!/2!
Add the 3 values -> 60480
另一个C被忽略,因为它的排列与之前的C是重复的(重复项)
此时我已经得到了从 ABCCJPUZ
到以J开头的单词之前的单词的排名。
ABCCJPUZ rank 1
...
... 60480 values
...
*HERE*
JABCCJPUZ rank 60481 LOCATION A
...
...
...
JACBZPUC rank ??? LOCATION B
我不确定如何获取位置A和B之间的值:
这是我用来找到60480个值的代码。
def perm(word):
return len(set(itertools.permutations(word)))
def swap(word, i, j):
word = list(word)
word[i], word[j] = word[j], word[i]
print word
return ''.join(word)
def compute(word):
if ''.join(sorted(word)) == word:
return 1
total = 0
sortedWord = ''.join(sorted(word))
beforeFirstCharacterSet = set(sortedWord[:sortedWord.index(word[0])])
print beforeFirstCharacterSet
for i in beforeFirstCharacterSet:
total += perm(swap(sortedWord,0,sortedWord.index(i)))
return total
这是我在网上找到的解决这个问题的方法。
考虑一个n字母的单词 { x1, x2, ... , xn }。我的解决方案基于这样一个想法:单词的编号将是两个数量的总和:
- 以字母表中比x1更小的字母开头的组合数量,以及
- 我们在以x1开头的排列中走了多远。
关键在于第二个数量恰好是单词 { x2, ... , xn } 的编号。这提示我们可以使用递归实现。
获取第一个数量有点复杂:
- 让 uniqLowers = { u1, u2, ... , um } = 所有比x1小的唯一字母
- 对于每个uj,计算以uj开头的排列数量。
- 把所有这些加起来。
我觉得我完成了第一步,但第二步不太确定如何完成。
这是Haskell的解决方案……我不懂Haskell =/,我正在尝试用Python编写这个程序。
3 个回答
在字母顺序的列表中,JABCCPUZ这个词的位置你已经知道,而JACBZPUC这个词的位置你想要找。其实这两个词的开头字母都是J。所以,找JACBZPUC相对于JABCCPUZ的位置,就等于去找去掉开头J之后,这两个词的相对位置。这样一来,你就回到了最开始想解决的问题,只不过这次的词少了一个字母。
如果你重复这个过程多次,最后会剩下一个只有一个字母的词C。一个只有一个字母的词的位置总是1,所以你可以把这个1和之前所有的相对位置加起来,就能得到它的绝对位置。
你找到的解决方案的第二部分,我觉得也是我想要建议的内容:
要从你所说的“位置A”到“位置B”,你需要找到单词 ACBZPUC
在它所有可能的排列中的位置。可以把这个看作是你算法的一个新问题,新的单词恰好比原来的单词短一个位置。
在计算字母排列的数量时,先找出实际第一个字母之前的字母数量这个想法是不错的。但是你的计算:
for `JACBZPUC`
sorted --> `ABCCJPUZ`
permutations that start with A -> 8!/2!
permutations that start with B -> 8!/2!
permutations that start with C -> 8!/2!
Add the 3 values -> 60480
是错误的。对于JACBZPUC这个组合,实际上只有8!/2! = 20160种排列方式,所以起始位置不能超过60480。在你的方法中,第一个字母是固定的,你只能对后面的七个字母进行排列。所以:
permutations that start with A: 7! / 2! == 2520
permutations that start with B: 7! / 2! == 2520
permutations that start with C: 7! / 1! == 5040
-----
10080
你不需要用2!来计算以C开头的排列,因为剩下的七个字母都是独一无二的;只剩下一个C。
下面是一个Python的实现示例:
def fact(n):
"""factorial of n, n!"""
f = 1
while n > 1:
f *= n
n -= 1
return f
def rrank(s):
"""Back-end to rank for 0-based rank of a list permutation"""
# trivial case
if len(s) < 2: return 0
order = s[:]
order.sort()
denom = 1
# account for multiple occurrences of letters
for i, c in enumerate(order):
n = 1
while i + n < len(order) and order[i + n] == c:
n += 1
denom *= n
# starting letters alphabetically before current letter
pos = order.index(s[0])
#recurse to list without its head
return fact(len(s) - 1) * pos / denom + rrank(s[1:])
def rank(s):
"""Determine 1-based rank of string permutation"""
return rrank(list(s)) + 1
strings = [
"ABC", "CBA",
"ABCD", "BADC", "DCBA", "DCAB", "FRED",
"QUESTION", "BOOKKEEPER", "JACBZPUC",
"AAAB", "AABA", "ABAA", "BAAA"
]
for s in strings:
print s, rank(s)