九宫格手机解锁有多少种情况?

0、写在前面:

本文的内容大概搬运自果壳和知乎的两篇文章,在结尾有注明参考。

安卓手势解锁是安卓手机解除锁定的密码方案,究竟这种方式一定有多少种可能呢?这是本文要讨论的问题。

1、问题定义

问题很简单:安卓的手势解锁是3*3的点阵,在这个点阵上的解锁手势一共有多少种情况?这里一个合格的解锁手势轨迹必须满足以下两个条件:

  • 至少连接点阵中的四个点。
  • 手势的轨迹不能跨过一个还没有经过的节点。
  • 不允许重复经过某个定点两次。

2、问题转化

为了方便,这里将点阵中的每个点用一个数字代替,1到9九个数字分别代表点阵中的一个点。这样,一个解锁手势可以对应到一个由1到9数字组成的字符串(该字符串中没有重复)。

去掉第二个限制条件,一种解锁手势正好对应一种1到9的排列。连接四个点的解锁手势的所有情况就是9选4的全排列,连接5个点的就是9选5的全排列,以此类推。

计算全排列的比较容易,接下来要解决的就是如何剔除那些不符合限制条件(手势的轨迹不能跨过一个还没有经过的节点)的手势。在3*3的点阵中,不符合条件的情况(也就是两个点的连接过程中跨过点的情况)比较有限,这里我们将其全部列出。

'13': '2', '46': '5', '79': '8', '17': '4', '28': '5', '39': '6', '19': '5', '37': '5', 
'31': '2', '64': '5', '97': '8', '71': '4', '82': '5', '93': '6', '91': '5', '73': '5'

上面可以看出,这种情况主要有16中(每种用一个k-v对来表示)。每一对列出了跨过点的情况,比如13连接会跨过2。

下面通过程序用全排列的思路列举出所有可能的手势情况,用一个数字字符串表示,并剔除掉其中不符合条件。剔除的思路很简单:对于每一种k-v对表示的跨过点的情况,如果k和v在表示手势的字符串中出现,并且没有出现在k出现的位置之前,那么这种情况应该被剔除。下面是代码(Python):

    from itertools import chain, permutations

    impossible = {'13': '2', '46': '5', '79': '8', '17': '4', '28': '5', '39': '6', '19': '5', '37': '5', '31': '2', '64': '5', '97': '8', '71': '4', '82': '5', '93': '6', '91': '5', '73': '5'}

    def counts():
            count = 0
            all_list = chain(*(permutations('123456789', i) for i in range(4, 9 + 1)))
            for e in all_list:
                    e_str = ''.join(e)
                    for k,v in impossible.items():
                            if k in e_str and v not in e_str[:e_str.find(k)]:
                                    break
                    else: 
                            count = count + 1
            return count

    print(counts())#389112

最后,程序运行的结果是一共有389112中情况。

3、进一步分析

下面稍微修改下程序,这些手势情况在不同的长度中是如何分布的?

    from itertools import chain, permutations

    impossible = {'13': '2', '46': '5', '79': '8', '17': '4', '28': '5', '39': '6', '19': '5', '37': '5', '31': '2', '64': '5', '97': '8', '71': '4', '82': '5', '93': '6', '91': '5', '73': '5'}

    def counts_n(n):
            iterlst = permutations('123456789', n)
            count = 0
            for i in iterlst:
                    stri = ''.join(i)
                    for k, v in impossible.items():
                            if k in stri and v not in stri[:stri.find(k)]:
                                    break
                    else: 
                            count += 1
            return count

    sum = 0  
    print("len  num  sum")
    for i in range(4,10):
            temp = counts_n(i)
            sum = sum + temp
            print(str(i)+"  "+str(temp)+"  "+str(sum))

结果如下:

len  num  sum
4  1624  1624
5  7152  8776
6  26016  34792
7  72912  107704
8  140704  248408
9  140704  389112

由此可见,该密码空间的绝大部分分布在连接8到9个点的情况。我们大部分人的手势密码都之后连接4到5个点,而这部分的搜索空间只有8776中可能,也就大概相当于4位数字密码的强度。

参考:

  1. 知乎https://www.zhihu.com/question/24905007/answer/29414497
  2. 果壳网http://www.guokr.com/article/49408/

你可能感兴趣的:(算法)