1. 程式人生 > >【演算法題】網易程式設計題:暗黑字串組合數

【演算法題】網易程式設計題:暗黑字串組合數

題目

一個字串僅由’A’,’B’,’C’三個字元組成,若字串中不存在’A’,’B’,’C’三個字元相鄰的子串(比如ABC,BAC等),則該字串稱為暗黑字串,否則稱為單純字串。

求長度為L的此種字串中有多少種是暗黑字串?

例子:
字串 AABBACCA,由於存在BAC子串,所以該字串為單純字串。
字串 AABBCC,由於不存在A,B,C相鄰構成的子串,所以該字串為暗黑字串。

長度為1的字串中,暗黑字串總共有3種(即”A”,”B”,”C”),
長度為2的字串中,暗黑字串總共有9種(兩個位置,每個位置有三張可能,3*3=9),
長度為3的字串中,暗黑字串總共有21種(三個位置,每個位置有三種可能,總共有3*3*3=27種,去除純淨字串3!=6,結果為27-6=21種)。

解題思路

1、數學方法:推導遞迴公式

n desc expression
0 0
1 A,B,C 3
2 第二位都有三種 3 * 3 = 9
3 當兩位相同(相當於只有一位)時,有三種;不同(即其餘情況)時,有兩種 f(1) * 3 + (f(2) - f(1)) * 2 = 9 + 12 = 21
4 同上 f(2) * 3 + (f(3) - f(2)) * 2 = 9 * 3 + 12 * 2 = 51
n f(n) = 3*f(n-2) + 2*(f(n-1) - f(n-2)) = f(n-2) + 2*(f(n-1)

根據公式,遞迴解決。

程式碼

    public static int getNum(int n) {
        if (n == 0) {
            return 0;
        }
        if (n == 1) {
            return 3;
        }
        if (n == 2) {
            return 9;
        }
        return getNum(n - 2) + 2 * getNum(n - 1);
    }

2、遍歷累加(動態規劃)

只要不出現(A、B、C)即可,則每增加一位,要考慮結尾的兩位情況。所以維護一個HashMap來存放n=2的情況和對應的出現次數,每加一位,累加HashMap中對應的value,最後累加HashMap中所有value值。

注:雖然有3層for迴圈,但是第二層和第三層迴圈次數是常量,由規定的abc決定,第二層是9次,第三層是3次,只是為了避免程式碼塊重複採用的for。

程式碼

public static int getNum2(int n) {
        String rule = "abc";
        if (n == 0) {
            return 0;
        }
        if (n == 1) {
            return rule.length();
        }
        if (n == 2) {
            return rule.length() * rule.length();
        }

        Map<String, Integer> map = new HashMap<>();
        init(map, 1);

        for (int i = 0; i <= n - rule.length(); i++) {
            Map<String, Integer> m = new HashMap<>();
            for (String s : map.keySet()) {
                int v = map.get(s);
                for (int r = 0; r < rule.length(); r++) {
                    char c = rule.charAt(r);
                    if (isDark(s + c)) {
                        String tmp = s.substring(1) + c;
                        if (m.containsKey(tmp)) {
                            m.put(tmp, m.get(tmp) + v);
                        } else {
                            m.put(tmp, v);
                        }
                    }
                }
            }
            map = m;
        }
        int cnt = 0;
        for (Integer j : map.values()) {
            cnt += j;
        }
        return cnt;
    }

    private static void init(Map<String, Integer> m, int i) {
        m.put("aa", i);
        m.put("ab", i);
        m.put("ac", i);
        m.put("ba", i);
        m.put("bb", i);
        m.put("bc", i);
        m.put("ca", i);
        m.put("cb", i);
        m.put("cc", i);
    }

    private static boolean isDark(String s) {
        return !(s.contains("abc") || s.contains("acb") || s.contains("bac") || s.contains("bca") || s.contains("cab")
                || s.contains("cba"));
    }

3、字串拼接

程式碼引用

/*
     * 主要想法:長度為L的暗黑字串可以根據最後兩個字元分成兩類,最後兩個字元是一樣的和最後兩個不一樣的情況。
     * 長度為L+1的暗黑字串可以由長度為L的暗黑字串生成。
     */
    public static long totalCount(long length) {
        if (length < 1)
            return 0;
        if (length == 1)
            return 3;
        /*
         * x1表示長度為L的字串最後兩個字元不一樣的暗黑字串個數(比如*AB,*AC等), 對於長度為2的暗黑字串,總個數為6
         */
        long x1 = 6;
        /*
         * y1表示一個字串最後連個字元一樣的暗黑字串個數(比如*AA,*BB等), 對於長度為2的暗黑字串,總個數為3
         */
        long y1 = 3;
        // start from string with length 2
        length = length - 2;
        while (length-- > 0) {
            long x2 = x1 + 2 * y1;
            long y2 = x1 + y1;
            x1 = x2;
            y1 = y2;
        }
        // 長度為length的字串中,暗黑字串總個數
        return x1 + y1;
    }