这道数学题是我偶然间发现的:
1223330000组合成十位数,问:共有多少种组合方式可使组合出的十位数只读一个零.
这题不需要用到什么高级的数学技巧,但是需要大量分类和枚举,于是,我想到了编程解决这一问题。
语言上我选择的是Python,另外,我的编程环境为PyCharm。
接下来,我们进入正文部分。
思路
我的思路是这样的:
遍历每一种组合情况,并对每一种情况进行检查,看是否符合题目中只读一个零的条件。
那么,这个程序就可以被分为两个部分:
1.遍历
2.检查
那么,我们开始逐步实现这一程序。
第一部分:遍历
我们需要用十个数字来组成十位数,这种情况下我们就要对每一个数位进行遍历,我们倒是可以用十个for循环嵌套在一起来实现,但是这显然会影响代码的可读性和美观,所以我们选择另外一种算法——递归。
什么是递归
如果对递归算法有所了解的,可以直接跳过这一部分。
递归算法,用一句话来解释就是:
递归:自己调用自己
专业一点叫做:
在函数的定义中使用函数自身的方法
为了各位读者更容易理解递归,我们来举一个简单的例子:
首先,定义一个数列,,。
接下来,我们编程来实现,输入,求的值。
由于这是一个首项为0,公差为2的等差数列,我们可以这样写:
n = int(input("Enter n:"))
an = 2 * (n-1)
print(an)
当然,如果用到刚才讲到的递归,我们就可以这样写:
def a(term):
if term == 1:
return 0
elif term >= 2:
a(term - 1)
n = int(input("Enter n:"))
print(a(n))
这样写可能会有些复杂,但是这一程序很好的体现了递归算法,看到这里:
def a(term):
…
elif term >= 2:
a(term - 1)
这就是前面所提到的,”自己调用自己“。
那么,这一程序是如何运行的呢?
手绘递归展示
注意图中还标有两个词:”回推“和”递推“
用我的方式来解释这两个词就是:
回推:一层一层向下调用的过程
递推:一层一层向上返回值的过程
以上,就是我对递归算法一个简单的介绍,如果想对递归有更多、更深入的了解,可以前往知乎上查看其他大佬对递归的解释。
用递归来实现遍历
运用递归,我们的实现方式就应该是这样:递归时,每回推一次,通过for循环遍历一个数位的可能情况,当所有数位确定完成时,进行下一部分。
与此同时,我们就也确定出了我们的函数需要的两个参数:已确定的数位(numlist),还未使用的数字(unused)。
当然,由于这个程序没有返回值,也就没有递推的过程了。
结合以上的思路,我们的程序会是这样:
def recu(numlist, unused):
if len(numlist) < 10:
# 长度小于十,说明还未组成十位数
used = []
for addnum in unused:
snumlist = numlist[:]
sunused = unused[:]
snumlist.append(addnum)
sunused.remove(addnum)
if snumlist[0] != 0:
recu(snumlist, sunused)
elif len(numlist) == 10:
# 长度等于十,说明十位数已经组成,那么检查是否能够读零
…
当然,我们还需要注意一点,在题目给出的可选数字中,有大量的重复数字,如果我们的遍历部分就这样写,对于同一个数字组合方式,程序会计算次,也就是说,程序给出答案的时长会多出整整288倍!
那么,我们要做的就是避免重复计算同一种情况,方法也很简单,每次遍历,检查addnum是否在列表used中,如果返回值为True,将addnum添加到列表used中。
修改后的程序会是这样:
def recu(numlist, unused):
if len(numlist) < 10:
# 长度小于十,说明还未组成十位数
used = []
for addnum in unused:
if addnum not in used:
used.append(addnum)
snumlist = numlist[:]
sunused = unused[:]
snumlist.append(addnum)
sunused.remove(addnum)
if snumlist[0] != 0:
recu(snumlist, sunused)
elif len(numlist) == 10:
# 长度等于十,说明十位数已经组成,那么检查是否能够读零
…
第二部分:检查
第二部分要做的工作是检查已组成的十位数是否符合”只读一个零“的条件。
并不是每一个数中的零都会被读出,那么,我们来梳理一下读零的规则:
- 处于每级末尾的零或由零组成的数串不会被读出「如:10 2300 3300」
- 处于数中部全部由零组成的数级,且前后数级不全是零的,会读一个零「如:12 0000 2333」
我们会发现,以上两条规则都是基于数级的,所以别的先抛开,我们要做的第一件事,就是将组成的十位数分级。
实现方式不难,我们先准备一个二维列表splitnum,用于存放被分级的十位数。
splitnum = [[], [], []]
然后,for循环遍历numlist,将每一个数字存放在最前面一个尚未存满的数级中(存满,即数级中已有四个数字)。
我的代码如下,需要注意的是,我的这种方式分解出来的数字是倒序的,在之后的程序,不要忽略这一点,当然,你也可以使分解数字正续排列,可以遍历index,甚至可以这样写:splitnum = [[numlist[0], …], …]
:
for i in range(0, len(numlist)):
for grade in splitnum:
if len(grade) < 4:
grade.append(numlist.pop())
break
结合读零规则的第一条,即”处于每级末尾的零或由零组成的数串不会被读出“,我们首先去掉处于每级末尾的零:
for grade in splitnum:
while True:
if grade[0] == 0 and len(grade) > 0:
del grade[0]
else:
break
注意这里的if grade[0] == 0 and len(grade) > 0
,在检测第一项为0的同时,也保证不把整个数级删空,这可以避免IndexError,同时,也因为整个数级均由零构成,可能会读零。
做完以上工作后,剩下的处于数中的0数串就全部都会被读出了,所以,我们将三个数级连接起来,再检查里面有几个由零组成的数串即可:
for grade in splitnum:
# 将每一级连接起来
for num in grade:
check.append(num)
while True:
if check[0] == 0:
del check[0]
else:
break
# 将连在一起的零变为单独的一个零
zero = False
idx = 0
oldcheck = check[:]
for num in oldcheck:
if num == 0:
if zero:
del check[idx]
idx -= 1
if not zero:
zero = True
else:
zero = False
idx += 1
if check.count(0) == 1:
result += 1
完工
至此,我们程序的主要部分就已全部编写完成了,加以整理,即可编写出完整的程序!
最后附上带有注释的完整代码:
# -*- coding: utf-8 -*-
"""
题目:
1223330000组合成十位数,问:共有多少种组合方式可使组合出的十位数只读一个零.
"""
def recu(numlist, unused):
if len(numlist) < 10:
# 长度小于十,说明还未组成十位数
used = []
for addnum in unused:
if addnum not in used:
used.append(addnum)
snumlist = numlist[:]
sunused = unused[:]
snumlist.append(addnum)
sunused.remove(addnum)
if snumlist[0] != 0:
recu(snumlist, sunused)
elif len(numlist) == 10:
# 长度等于十,说明十位数已经组成,那么检查是否能够读零
print(numlist, end=' ')
global result
# 首先按级进行分割
splitnum = [[], [], []]
for i in range(0, len(numlist)):
for grade in splitnum:
if len(grade) < 4:
grade.append(numlist.pop())
break
# 对每一级进行检查,看是否读零
check = []
for grade in splitnum:
# 末尾的零不会读出,所以去掉末尾的零
while True:
if grade[0] == 0 and len(grade) > 1:
del grade[0]
else:
break
# 将每一级连接起来
for num in grade:
check.append(num)
while True:
if check[0] == 0:
del check[0]
else:
break
# 将连在一起的零变为单独的一个零
zero = False
idx = 0
oldcheck = check[:]
for num in oldcheck:
if num == 0:
if zero:
del check[idx]
idx -= 1
if not zero:
zero = True
else:
zero = False
idx += 1
print("check:", check, end=" ")
if check.count(0) == 1:
result += 1
print("PASS")
else:
print("NOT PASS")
else:
print("Error")
if __name__ == "__main__":
alternative = [1, 2, 2, 3, 3, 3, 0, 0, 0, 0]
result = 0
recu([], alternative)
print(result)
运行结果:
…
[3, 0, 0, 0, 0, 3, 2, 3, 1, 2] check: [2, 1, 3, 2, 3, 0, 3] PASS
[3, 0, 0, 0, 0, 3, 2, 3, 2, 1] check: [1, 2, 3, 2, 3, 0, 3] PASS
[3, 0, 0, 0, 0, 3, 3, 1, 2, 2] check: [2, 2, 1, 3, 3, 0, 3] PASS
[3, 0, 0, 0, 0, 3, 3, 2, 1, 2] check: [2, 1, 2, 3, 3, 0, 3] PASS
[3, 0, 0, 0, 0, 3, 3, 2, 2, 1] check: [1, 2, 2, 3, 3, 0, 3] PASS
2460
Process finished with exit code 0
所以,我们得出了这道题目的答案——2460。
这是我第一次发表这种文章,内容质量和文笔都还有改进的空间,还请大家多多鼓励,如果有建议的话,欢迎在评论区留言!
网友评论