众所周知,手机图案密码是用3×3排列的点组成的图案,不过,谁会想到手机密码共有多少种呢?
不过最近有位同学问我这个问题,我试着研究一下,发现此题并不是那么简单。经过将近一天的尝试和失败,笔者有了一点想法,并编出了python代码,具体如下:
为了便于讨论,我们将手机图案的九个点编号,如下图所示:
编号
顺序从左到右,从上到下。
这里约定:
1. 密码图案的个数从1个到9个,即单个点也看作一种情况
2. 图案的每个点只允许使用一次
3. 图案有顺序之分,如1-5-8-9和9-8-5-1是两种不同的情况。
这样,一个图案可以看做一组互不相同的数的排列。
注意到以下规律:
I.
如果图案中含有连续的1-2-3之类的数,其实可以看做1-3,因为从1号点到3号点必然经过2好点,此处不妨称之为退化,即1-2-3退化为1-3,2-5-8退化为2-8。
注意:1-2-5-3不能退化,只有连续的三个数在同一行或同一列或两条对角线才满足退化的条件,并且此时三个数递增或递减。
因此可以退化的三个连续数共有16组,分别为:
1-2-3,4-5-6,7-8-9,1-4-7,2-5-8,3-6-9,1-5-9,3-5-7,
3-2-1,6-5-4,9-8-7,7-4-1,8-5-2,9-6-3,9-5-1,7-5-3
II.
如果排列中出现过连续的1和3,则不允许在后面的数字中使用2,因为之前1-3自动占用了2,像1-3-…-2是不允许的,其中1-3-2和1-3-4-6-2都是不允许的,不妨像把1-3-2这样的三个数(1和3连续出现,2可以与前面连续也可以不与前面连续)称为非法数组。
所有非法数组共有8种:
312,465,798,174,285,396,195,375,
132,645,978,714,825,936,915,735
注意:前两个数必须连续,第3个数可以连续也可以不连续。
接下来就要用python帮我们“算”了,首先要得到1-9九个数任取n( 0<n<10)的排列,这一步在python中比较简单只需要执行:
from itertools import permutations
items = [i for i in range(1, 10)]
count = 0
for j in range(1, 10):
for p in permutations(items, j):
arr = list(p)
这里,itertools模块中含有排列组合的函数,permutations()函数有两个参数,第一个参数是一个列表,第二个参数是一个正整数(n),也就是说从列表中任取n个元素做排列,每个排列是一个元组(tuple),并将其存到一个列表中,我们用for循环分别取出1,2,3…9个元素,每次再用for循环得到每个排列p,将p转化为列表(list),交给我们的一个函数ifsolve(arr),来判断这个列表是否符合要求,符合返回1,不符合返回0,接下来:
if (ifsolve(arr) == 1):
count += 1
f.write(str(arr) + ' ')
f.write(str(count))
f.close()
如果符合要求,那么count递增1,并将其写入文件data.txt,哦别忘了在开头加上
f = open("data.txt", mode = 'w')
这样可以打开data.txt,(如果没有就会自动新建一个叫data的txt文件)。
以下为完整代码:
f = open("data.txt", mode = 'w')
def ifsolve(arr):
ret = 1
for i in range(0, len(arr) - 2):
for j in range(i + 1, len(arr) - 1):
for k in range(j + 1, len(arr)):
li1 = [arr[i], arr[j], arr[k]]
li2 = sorted(li1)
if (li2 == [1, 2, 3] or
li2 == [4, 5, 6] or
li2 == [7, 8, 9] or
li2 == [1, 4, 7] or
li2 == [2, 5, 8] or
li2 == [3, 6, 9] or
li2 == [1, 5, 9] or
li2 == [3, 5, 7]):
if ((li1[0] < li1[1]) and (li1[1] < li1[2]) and (k - j == 1) and (j - i == 1)):
ret = 0
elif ((li1[2] < li1[1]) and (li1[1] < li1[0]) and (k - j == 1) and (j - i == 1)):
ret = 0
elif (li1[0] < li1[2]) and (li1[2] < li1[1]) and (j - i == 1):
ret = 0
elif (li1[1] < li1[2]) and (li1[2] < li1[0]) and (j - i == 1):
ret = 0
return ret
from itertools import permutations
items = [i for i in range(1, 10)]
count = 0
for j in range(1, 10):
for p in permutations(items, j):
arr = list(p)
if (ifsolve(arr) == 1):
count += 1
f.write(str(arr) + ' ')
f.write(str(count))
f.close()
下面是完整代码的截图:
完整代码
我们得到arr之后,将其传入函数,函数的返回值为ret,如果arr不满足条件,返回0,否则返回1,我们用3个for循环嵌套按顺序取出arr中的任意三个数字,并将其存入li1,将li1用sorted函数排列得到从小到大的列表li2,如果li2是[1, 2, 3] ,[4, 5, 6], [7, 8, 9],[1, 4, 7] ,[2, 5, 8],[3, 6, 9],[1, 5, 9] ,[3, 5, 7]的一种情况,则该arr有不满足条件的可能,需要进一步判断,
if ((li1[0] < li1[1]) and (li1[1] < li1[2]) and (k - j == 1) and (j - i == 1)):
ret = 0
elif ((li1[2] < li1[1]) and (li1[1] < li1[0]) and (k - j == 1) and (j - i == 1)):
ret = 0
以上几行代码判断的是规律中的I,即连续的1-2-3,或3-2-1之类的数,条件k - j == 1,
j - i == 1保证它们3个数是连续的,条件(li1[0] < li1[1]),(li1[1] < li1[2])保证li1是递增的,同理,
(li1[2] < li1[1]), (li1[1] < li1[0])保证li1是递减的。
下面几行代码是规律II的实现:
elif (li1[0] < li1[2]) and (li1[2] < li1[1]) and (j - i == 1):
ret = 0
elif (li1[1] < li1[2]) and (li1[2] < li1[0]) and (j - i == 1):
ret = 0
其中j - i == 1保证前两个数是连续的,f (li1[0] < li1[2]) and (li1[1])和(li1[1] < li1[2]) and (li1[2] < li1[0])
则保证li1是1-3-…-2 ,和3-1-…-2这种非法数组的顺序。
OK,到这里程序终于完成,电脑帮我们计算结果,在笔者的电脑上,打开data.txt,拖到最后一行,结果是389497,网上的标准答案是389112。
这是因为:
1.笔者计算的情况包含1个点到3个点的情况,而网上的答案从4个点开始计算,不包含小于4个点的情况。
2.笔者计算的是所有情况都是退化后的结果,即1-2-3-6-9,1-3-6-9,1-2-3-9在笔者的结果中都没有,它们被都被当成1-3-9。
文章到这里就结束了,喜欢的话就投个币,点个赞吧。