Calculate the number of 'words' that can be formed from all the letters in
the word CALENDAR if C and A must be together, but N and D are not together.
用单词CALENDAR的字母重新组合成不同的单词,使得每个单词中的字母C与至少一个A在一起,
并且D和N不在一起,求满足条件的单词总个数。
本题来自加拿大的数学教材。由于收录此题的教材也给出了错误的参考答案,所以写一段代码来运行一下来检验计算结果的正确性,给正确的答案伸冤,是有价值的。
本文是通过Python的暴力枚举+搜索来得到答案,仅作为对答案的检验,对此题的数学解答并没有帮助,且本文不会对正确的解法作任何形式的提示 😃😃😃 然而,作者认为代码本身的算法很有意思,所以在此分享代码,兼自己心路历程的一个记录。
将代码分为两个部分,第一个部分是构造一个递归函数,将所有的组合,不管重复且是否符合要求都加入到一个存储单词的列表中;第二个部分是遍历整个列表,检查每一个元素是否符合要求,不符合便将其剔除。代码本身不难,however,中间有几个坑仍是值得注意。
生成所有单词
首先是生成所有可能单词的递归函数 assembling。
该函数接受两个参数:current_string和current_list. current_string是当前该循环下这一个单词的状态,已经有了哪几个字母;current_list存储了还有几个字母没有添加。有了这两个变量,当单词已经有n个字母长时,函数在for循环下将按剩下单词的顺序给current_string添加第n+1个字母,同时在列表中删除那个字母,并将添加后的current_string和删除后的current_list通过递归调用传给原函数,再添加第n+2个字母。
注意:
- 同一个函数内,循环的每一步添加的都是第n+1个字母,而不是单纯的for循环下连续给同一个字符串添加第n+1,n+2,n+3…个字母,只有循环添加第n+1个字母才能达到穷举。而这正要靠for循环中声明一个替身变量copy_string,来替current_string接下增加的字母,每进入循环的下一步,copy_string的值就被刷新一次,这样才能保证同一个循环每一步都是在字符串的同一个位置加上不同的字母,以便传给下一个函数。
- 在本段代码的最后一行将列表转换为了集合,这样操作一来是为了剔去重复元素,而来是为了在后续要去掉不符合要求的元素时就可以直接用集合的取补集方法,而至于要取补集而不是直接在循环里删除那个元素的原因在下段代码解释。
characters, words= 'C A L E N D A R'.split(' '),[]
def assembling(current_string = '', current_list=None):
if current_list is None: current_list = characters
if len(current_string) == 8: words.append(current_string)
else:
for i in range(len