题目概述
题目链接:点我做题
题解
一、动态规划
字符串类的题如果要考虑动态规划都可以考虑以什么东西结尾的目标量,比如本题,先定义一个映射
a
−
0
,
e
−
1
,
i
−
2
,
o
−
3
,
u
−
4
a-0,e-1,i-2,o-3,u-4
a−0,e−1,i−2,o−3,u−4定义
f
(
i
,
j
)
(
j
=
0
,
1
,
2
,
3
,
4
)
f(i,j)(j=0,1,2,3,4)
f(i,j)(j=0,1,2,3,4)表示以字符j结尾的长度为i的串在本题规则下的数量,下面我们分析一下规则:
规则一直在告诉我们后面后面,但其实这是一个误导,我们反而应该考虑前面(也应了我们的假设),下面考虑以j结尾的字符前面都有可能有哪些情况:
以
j
=
0
j=0
j=0为例,以a结尾的字符,前面可不可以是a呢,不可以,第一条规则说a后面只能跟着e;可不可以是e呢,第二条规则告诉我们可以;前面可不可以是i呢,可以,规则3只规定了i后面不能跟i,其他没有限制;前面可不可以是o呢,不可以,第四条规则规定o后面只能是i或u;前面能不能是u呢,可以,最后一条规则规定u后面只能跟a。
同理可以把其他字符前面允许的字符规定分析出来:
a
前
:
e
,
i
,
u
e
前
:
a
,
i
i
前
:
e
,
o
o
前
:
i
u
前
:
i
,
o
a前:e,i,u\\ e前:a,i\\ i前:e,o\\ o前:i\\ u前:i,o\\
a前:e,i,ue前:a,ii前:e,oo前:iu前:i,o
长度为i的,以j结尾的串的数量不就等于去掉j字符后,长度为i-1,结尾字符为j字符允许的前缀的字符串的数量嘛,所以有状态转移方程:
a
:
f
(
i
,
0
)
=
f
(
i
−
1
,
1
)
+
f
(
i
−
1
,
2
)
+
f
(
i
−
1
,
4
)
e
:
f
(
i
,
1
)
=
f
(
i
−
1
,
0
)
+
f
(
i
−
1
,
2
)
i
:
f
(
i
,
2
)
=
f
(
i
−
1
,
1
)
+
f
(
i
−
1
,
3
)
o
:
f
(
i
,
3
)
=
f
(
i
−
1
,
2
)
u
:
f
(
i
,
4
)
=
f
(
i
−
1
,
2
)
+
f
(
i
−
1
,
3
)
a:f(i,0)=f(i-1,1)+f(i-1,2)+f(i-1,4)\\ e:f(i,1)=f(i-1,0)+f(i-1,2)\\ i:f(i,2)=f(i-1,1)+f(i-1,3)\\ o:f(i,3)=f(i-1,2)\\ u:f(i,4)=f(i-1,2)+f(i-1,3)\\
a:f(i,0)=f(i−1,1)+f(i−1,2)+f(i−1,4)e:f(i,1)=f(i−1,0)+f(i−1,2)i:f(i,2)=f(i−1,1)+f(i−1,3)o:f(i,3)=f(i−1,2)u:f(i,4)=f(i−1,2)+f(i−1,3)
显然有初始条件
f
(
0
,
i
)
=
0
f(0,i) = 0
f(0,i)=0,
f
(
1
,
i
)
=
1
f(1,i)=1
f(1,i)=1
接下来进行动态规划就可以解决了,用一个二维数组dp储存计算过的状态就可以了,注意题中说的要%
1
e
9
+
7
1e9+7
1e9+7
class Solution {
public:
int countVowelPermutation(int n)
{
vector<vector<long long>> dp(n + 1, vector<long long>(5, 0));
long long mod = 1e9 + 7;
for (int i = 0; i < 5; i++)
{
dp[1][i] = 1;
}
for (int i = 2; i <= n; ++i)
{
dp[i][0] = (dp[i - 1][1] + dp[i - 1][2] + dp[i - 1][4]) % mod;
dp[i][1] = (dp[i - 1][0] + dp[i - 1][2]) % mod;
dp[i][2] = (dp[i - 1][1] + dp[i - 1][3]) % mod;
dp[i][3] = (dp[i - 1][2]) % mod;
dp[i][4] = (dp[i - 1][2] + dp[i - 1][3]) % mod;
}
long long ret = 0;
for (int i = 0; i < 5; ++i)
{
ret += dp[n][i];
}
return ret % (mod);
}
};
时间复杂度:
O
(
C
n
)
O(Cn)
O(Cn),C为元音字符的个数。
空间复杂度:本题的状态只和前置状态有关,我们可以只记录上一轮的状态而不必开辟dp数组,所以空间复杂度其实可以优化到O©,C为元音字符个数。
二、矩阵法
观察我们的状态转移方程:
a
:
f
(
i
,
0
)
=
f
(
i
−
1
,
1
)
+
f
(
i
−
1
,
2
)
+
f
(
i
−
1
,
4
)
e
:
f
(
i
,
1
)
=
f
(
i
−
1
,
0
)
+
f
(
i
−
1
,
2
)
i
:
f
(
i
,
2
)
=
f
(
i
−
1
,
1
)
+
f
(
i
−
1
,
3
)
o
:
f
(
i
,
3
)
=
f
(
i
−
1
,
2
)
u
:
f
(
i
,
4
)
=
f
(
i
−
1
,
2
)
+
f
(
i
−
1
,
3
)
a:f(i,0)=f(i-1,1)+f(i-1,2)+f(i-1,4)\\ e:f(i,1)=f(i-1,0)+f(i-1,2)\\ i:f(i,2)=f(i-1,1)+f(i-1,3)\\ o:f(i,3)=f(i-1,2)\\ u:f(i,4)=f(i-1,2)+f(i-1,3)\\
a:f(i,0)=f(i−1,1)+f(i−1,2)+f(i−1,4)e:f(i,1)=f(i−1,0)+f(i−1,2)i:f(i,2)=f(i−1,1)+f(i−1,3)o:f(i,3)=f(i−1,2)u:f(i,4)=f(i−1,2)+f(i−1,3)
若定义向量
f
(
i
)
⃗
=
(
f
(
i
,
0
)
,
f
(
i
,
1
)
,
f
(
i
,
2
)
,
f
(
i
,
3
)
,
f
(
i
,
4
)
)
T
\vec{f(i)}=(f(i,0),f(i,1),f(i,2),f(i,3),f(i,4))^{T}
f(i)=(f(i,0),f(i,1),f(i,2),f(i,3),f(i,4))T,考虑矩阵知识,状态转移方程可以写为:
f
(
i
)
⃗
=
[
0
1
1
0
1
1
0
1
0
0
0
1
0
1
0
0
0
1
0
0
0
0
1
1
0
]
f
(
i
−
1
)
⃗
=
A
f
(
i
−
1
)
⃗
\vec{f(i)}=\begin{bmatrix} 0 & 1&1&0&1 \\ 1 & 0&1&0&0\\ 0&1&0&1&0\\ 0&0&1&0&0\\ 0&0&1&1&0\\ \end{bmatrix}\vec{f(i-1)}=A\vec{f(i-1)}
f(i)=⎣⎢⎢⎢⎢⎡0100010100110110010110000⎦⎥⎥⎥⎥⎤f(i−1)=Af(i−1)
且
f
(
1
)
⃗
=
(
1
,
1
,
1
,
1
,
1
)
T
\vec{f(1)}=(1,1,1,1,1)^{T}
f(1)=(1,1,1,1,1)T,要得到
f
(
n
)
⃗
\vec{f(n)}
f(n)只要让f(1)乘
A
n
−
1
A^{n-1}
An−1即可.求矩阵的幂有一些快一点的数学算法,比如快速幂算法可以使用。