1. 程式人生 > >九宮格手機解鎖有多少種情況?

九宮格手機解鎖有多少種情況?

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/