1. 程式人生 > >[LeetCode] Android Unlock Patterns 安卓解鎖模式

[LeetCode] Android Unlock Patterns 安卓解鎖模式

Given an Android 3x3 key lock screen and two integers m and n, where 1 ≤ m ≤ n ≤ 9, count the total number of unlock patterns of the Android lock screen, which consist of minimum of m keys and maximum n keys.

Rules for a valid pattern:

  1. Each pattern must connect at least m keys and at most n keys.
  2. All the keys must be distinct.
  3. If the line connecting two consecutive keys in the pattern passes through any other keys, the other keys must have previously selected in the pattern. No jumps through non selected key is allowed.
  4. The order of keys used matters.

Explanation:

| 1 | 2 | 3 |
| 4 | 5 | 6 |
| 7 | 8 | 9 |

Invalid move: 4 - 1 - 3 - 6 
Line 1 - 3 passes through key 2 which had not been selected in the pattern.

Invalid move: 4 - 1 - 9 - 2
Line 1 - 9 passes through key 5 which had not been selected in the pattern.

Valid move: 2 - 4 - 1 - 3 - 6
Line 1 - 3 is valid because it passes through key 2, which had been selected in the pattern

Valid move: 6 - 5 - 4 - 1 - 9 - 2
Line 1 - 9 is valid because it passes through key 5, which had been selected in the pattern.

Example:
Given m = 1, n = 1, return 9.

Credits:
Special thanks to @elmirap for adding this problem and creating all test cases.

這道題乍一看題目這麼長以為是一個設計題,其實不是,這道題還是比較有意思的,起碼跟實際結合的比較緊密。這道題說的是安卓機子的解鎖方法,有9個數字鍵,如果密碼的長度範圍在[m, n]之間,問所有的解鎖模式共有多少種,注意題目中給出的一些非法的滑動模式。那麼我們先來看一下哪些是非法的,首先1不能直接到3,必須經過2,同理的有4到6,7到9,1到7,2到8,3到9,還有就是對角線必須經過5,例如1到9,3到7等。我們建立一個二維陣列jumps,用來記錄兩個數字鍵之間是否有中間鍵,然後再用一個一位陣列visited來記錄某個鍵是否被訪問過,然後我們用遞迴來解,我們先對1呼叫遞迴函式,在遞迴函式中,我們遍歷1到9每個數字next,然後找他們之間是否有jump數字,如果next沒被訪問過,並且jump為0,或者jump被訪問過,我們對next呼叫遞迴函式。數字1的模式個數算出來後,由於1,3,7,9是對稱的,所以我們乘4即可,然後再對數字2呼叫遞迴函式,2,4,6,9也是對稱的,再乘4,最後單獨對5呼叫一次,然後把所有的加起來就是最終結果了,參見程式碼如下:

解法一:

class Solution {
public:
    int numberOfPatterns(int m, int n) {
        int res = 0;
        vector<bool> visited(10, false);
        vector<vector<int>> jumps(10, vector<int>(10, 0));
        jumps[1][3] = jumps[3][1] = 2;
        jumps[4][6] = jumps[6][4] = 5;
        jumps[7][9] = jumps[9][7] = 8;
        jumps[1][7] = jumps[7][1] = 4;
        jumps[2][8] = jumps[8][2] = 5;
        jumps[3][9] = jumps[9][3] = 6;
        jumps[1][9] = jumps[9][1] = jumps[3][7] = jumps[7][3] = 5;
        res += helper(1, 1, 0, m, n, jumps, visited) * 4;
        res += helper(2, 1, 0, m, n, jumps, visited) * 4;
        res += helper(5, 1, 0, m, n, jumps, visited);
        return res;
    }
    int helper(int num, int len, int res, int m, int n, vector<vector<int>> &jumps, vector<bool> &visited) {
        if (len >= m) ++res;
        ++len;
        if (len > n) return res;
        visited[num] = true;
        for (int next = 1; next <= 9; ++next) {
            int jump = jumps[num][next];
            if (!visited[next] && (jump == 0 || visited[jump])) {
                res = helper(next, len, res, m, n, jumps, visited);
            }
        }
        visited[num] = false;
        return res;
    }
}; 

下面這種方法很簡潔,但是不容易理解,講解請看這個帖子。其中used是一個9位的mask,每位對應一個數字,如果為1表示存在,0表示不存在,(i1, j1)是之前的位置,(i, j)是當前的位置,所以滑動是從(i1, j1)到(i, j),中間點為((i1+i)/2, (j1+j)/2), 這裡的I和J分別為i1+i和j1+j,還沒有除以2,所以I和J都是整數。如果I%2或者J%2不為0,說明中間點的座標不是整數,即中間點不存在,如果中間點存在,如果中間點被使用了,則這條線也是成立的,可以呼叫遞迴,參見程式碼如下:

解法二:

class Solution {
public:
    int numberOfPatterns(int m, int n) {
        return count(m, n, 0, 1, 1);
    }
    int count(int m, int n, int used, int i1, int j1) {
        int res = m <= 0;
        if (!n) return 1;
        for (int i = 0; i < 3; ++i) {
            for (int j = 0; j < 3; ++j) {
                int I = i1 + i, J = j1 + j, used2 = used | (1 << (i * 3 + j));
                if (used2 > used && (I % 2 || J % 2 || used2 & (1 << (I / 2 * 3 + J / 2)))) {
                    res += count(m - 1, n - 1, used2, i, j);
                }
            }
        }
        return res;
    }
};

參考資料: