1. 描述

仅由三个字符A、B、C构成字符串,且字符串任意相邻三个元素不能完全相同。如“ACCCAB”不合法,“ABBCBCA”合法。求满足条件的长度为n的字符串个数。假定不考虑整数溢出,要求时间和空间复杂度不高于O(N)。

2. Hints

  • 1、考虑使用动态规划,将长度为N的字符串个数表示成长度为N-1的字符串个数的递归关系;
  • 2、使用滚动数组降低空间复杂度;
  • 3、矩阵乘幂降低时间复杂度。

3. Solution

若当前已经有长度为n-1的合法字符串,则在末端增加一个字符,形成长度为n的字符串,n-1的字符串可分为“末端两字符相等”和“末端两字符不等”,分别记为\(dp[n-1][0]\)和\(dp[n-1][1]\),长度为n的字符串都可以这样划分,于是:
$$
\begin{cases}
dp[n][0] = dp[n-1][0]*2 + dp[n-1][1]*2 \\
dp[n][1] = dp[n-1][0]
\end{cases}
$$
其初始条件为\(dp[1][0] = 3, dp[1][1] = 0\);

状态转移方程为
$$
\begin{cases}
dp[n][0] = dp[n-1][0]*2 + dp[n-1][1]*2 \\
dp[n][1] = dp[n-1][0]
\end{cases}
$$
其可以通过滚动数组简化:
$$
\begin{cases}
dp[0] = dp[0]*2 + dp[1]*2 \\
dp[1] = dp[0]
\end{cases}
$$
由此可以将空间复杂度降为O(1)

4. 代码

#include<iostream>
using namespace std;

int calculateCount(int);
int main(int argc, char** argv) {
    int n;
    cin >> n;

    int accumulation = calculateCount(n);
    cout << accumulation << endl;
    return 0;
}

int calculateCount(int n) {
    int accumulation = 0;
    int dp0 = 3, dp1 = 0;

    for(int i = 1; i < n; i++) {
        int temp = dp0;
        dp0 = 2*dp0 + 2*dp1;
        dp1 = temp;
    }
    
    accumulation = dp0+dp1;

    return accumulation;
}

5. 优化

此题还可以简化时间复杂度,方法就是题目中的提示3。
由矩阵的状态方程:
$$
\begin{cases}
dp[0] = 2*dp[0] + 2*dp[1] \\
dp[1] = dp[0]
\end{cases}
$$
得到矩阵形式:
$$
\begin{equation}
(dp[0] dp[1])_{new} = (dp[0] dp[1])_{old} \cdot \bigl( \begin{matrix} 2 & 1 \\ 2 & 0 \end{matrix} \bigr)
\nonumber
\end{equation}
$$
从而得到:
$$
\begin{equation}
\begin{split}
(dp[0] dp[1])_{n} &= (dp[0] dp[1])_{n-1} \cdot \bigl( \begin{matrix} 2 & 1 \\ 2 & 0 \end{matrix} \bigr) \\
&= (dp[0] dp[1])_{n-2} \cdot \bigl( \begin{matrix} 2 & 1 \\ 2 & 0 \end{matrix} \bigr) \bigl( \begin{matrix} 2 & 1 \\ 2 & 0 \end{matrix} \bigr) \\
&= \ldots \\
&= (dp[0] dp[1])_{1} \cdot \bigl( \begin{matrix} 2 & 1 \\ 2 & 0 \end{matrix} \bigr)^{n-1}
\end{split}
\nonumber
\end{equation}
$$

新建一个矩阵求幂的类,并据此写出完整代码,以下只给出类实现:

#ifndef POWMATRIX_H
#define POWMATRIX_H

class PowMatrix {
    public:
        PowMatrix(int dimension);   //构造函数
        virtual ~PowMatrix();       //析构函数
        void setMatrix(int* matrix);    //设置矩阵
        int multiply(int row,int column);   //两数组对应相乘
        void pow(int count);        //求幂函数
        int* getResult() {          //得到结果
            return result;
        }
    private:
        int dimension;      //矩阵维度
        int* matrix;        //需要求幂的初始矩阵
        int* result;        //求幂之后存放结果的矩阵
};

#endif // POWMATRIX_H


PowMatrix::PowMatrix(int dimension) {
    this->dimension = dimension;            //初始化维度
    matrix = new int[dimension*dimension];  //初始化分配matrix空间
    result = new int[dimension*dimension];  //初始化分配存放结果的矩阵的内存空间
}

PowMatrix::~PowMatrix() {
    delete matrix;
    delete result;
}

void PowMatrix::setMatrix(int* matrix) {    //设置需要自乘的矩阵值,用一维数组形式逐行存放
    int len = dimension*dimension;
    for(int i = 0; i < len; i++) {
        (this->matrix)[i] = matrix[i];
    }
}

int PowMatrix::multiply(int row, int column) {  //result矩阵第row行和matrix矩阵第column列相乘
    int val = 0;
    for(int i = 0, j = 0; i < dimension; i++,j++) {
        val += ( result[row*dimension+i] ) * ( matrix[j*dimension+column] );
    }
    return val;
}

void PowMatrix::pow(int count) {                //矩阵求幂函数
    //以下为建立单位矩阵
    for(int i = 0; i < dimension*dimension; i++) {
        if(i/dimension == i%dimension) {        //判断一维数组模拟矩阵的实际对角线位置
            result[i] = 1;
        } else {
            result[i] = 0;
        }
    }
    //矩阵的count次幂
    for(int k = 0; k < count; k++) {            //第k次自乘
        int* temp = new int[dimension*dimension];
        //以下循环,逐次求出矩阵row行column列的值
        for(int row = 0; row < dimension; row++) {
            for(int column = 0; column < dimension; column++) {
                temp[row*dimension+column] = multiply(row,column);
            }
        }
        //将temp矩阵赋值给result矩阵,即更新result矩阵
        for(int j = 0; j < dimension*dimension; j++) {
            result[j] = temp[j];
        }
    }
}

代码可在此处下载