「这是我参与2022首次更文挑战的第1天,活动详情查看:2022首次更文挑战」。
给你一个整数 n,请你帮忙统计一下我们可以按下述规则形成多少个长度为 n 的字符串:
- 字符串中
- 的每个字符都应当是小写元音字母(
'a','e','i','o','u')
由于答案可能会很大,所以请你返回 模 10^9 + 7 之后的结果。
示例 1:
输入:n = 1
输出:5
解释:所有可能的字符串分别是:"a", "e", "i" , "o" 和 "u"。
示例 2:
输入:n = 2
输出:10
解释:所有可能的字符串分别是:"ae", "ea", "ei", "ia", "ie", "io", "iu", "oi", "ou" 和 "ua"。
示例 3:
输入:n = 5
输出:68
这道题我最开始的想法是把每个数都遍历出来, 但是看到需要取模之后
首先我先判断0和1的两种情况
if (n == 0) return 0
if (n == 1) return 5
然后我将n=1的五种情况列出来,同时也是需要进行计算的五个基础字符,并将其存入map中,初始数值都是1个。
let a = ["a", "e", "i", "o", "u"]
let map = new Map()
for (let i = 0; i < a.length; i++) {
map.set(a[i], 1)
}
接下来就是递归遍历的阶段。
首先确定终止条件,是n==0 时递归终止
同时我们将map进行一次拷贝,用于计算数量
let tem = []
map.forEach(v => {
tem.push(v)
})
递归时我们遍历map的五个字符,每遇到一个字符,就去掉其数量,并对其对应应该添加的字符的个数加上当前字符的个数。
for (const m of map) {
switch (m[0]) {
case "a":
map.set("e", map.get('e') + 1 * tem[0] % mod)
map.set("a", map.get('a') - 1 * tem[0] % mod)
break;
case "e":
res = res + 1 * tem[1] % mod
map.set("i", map.get('i') + 1 * tem[1] % mod)
map.set("a", map.get('a') + 1 * tem[1] % mod)
map.set("e", map.get('e') - 1 * tem[1] % mod)
break;
case "i":
res = res + 3 * tem[2] % mod
map.set("i", map.get('i') - 1 * tem[2] % mod)
map.set("a", map.get('a') + 1 * tem[2] % mod)
map.set("e", map.get('e') + 1 * tem[2] % mod)
map.set("o", map.get('o') + 1 * tem[2] % mod)
map.set("u", map.get('u') + 1 * tem[2] % mod)
break;
case "o":
res = res + 1 * tem[3] % mod
map.set("i", map.get('i') + 1 * tem[3] % mod)
map.set("u", map.get('u') + 1 * tem[3] % mod)
map.set("o", map.get('o') - 1 * tem[3] % mod)
break;
case "u":
map.set("u", map.get('u') - 1 * tem[4] % mod)
map.set("a", map.get('a') + 1 * tem[4] % mod)
break;
default:
break;
}
}
总结
var countVowelPermutation = function (n) {
if (n == 0) return 0
if (n == 1) return 5
let mod = 1000000007
let a = ["a", "e", "i", "o", "u"]
let map = new Map()
for (let i = 0; i < a.length; i++) {
map.set(a[i], 1)
}
let res = 5
dp(n - 1)
return res % mod
function dp (n) {
if (n == 0) return
let tem = []
map.forEach(v => {
tem.push(v)
})
for (const m of map) {
switch (m[0]) {
case "a":
map.set("e", map.get('e') + 1 * tem[0] % mod)
map.set("a", map.get('a') - 1 * tem[0] % mod)
break;
case "e":
res = res + 1 * tem[1] % mod
map.set("i", map.get('i') + 1 * tem[1] % mod)
map.set("a", map.get('a') + 1 * tem[1] % mod)
map.set("e", map.get('e') - 1 * tem[1] % mod)
break;
case "i":
res = res + 3 * tem[2] % mod
map.set("i", map.get('i') - 1 * tem[2] % mod)
map.set("a", map.get('a') + 1 * tem[2] % mod)
map.set("e", map.get('e') + 1 * tem[2] % mod)
map.set("o", map.get('o') + 1 * tem[2] % mod)
map.set("u", map.get('u') + 1 * tem[2] % mod)
break;
case "o":
res = res + 1 * tem[3] % mod
map.set("i", map.get('i') + 1 * tem[3] % mod)
map.set("u", map.get('u') + 1 * tem[3] % mod)
map.set("o", map.get('o') - 1 * tem[3] % mod)
break;
case "u":
map.set("u", map.get('u') - 1 * tem[4] % mod)
map.set("a", map.get('a') + 1 * tem[4] % mod)
break;
default:
break;
}
}
n--
dp(n)
}
};
上述代码时间复杂度过高。
所以我们还是使用动态规划的方案
题目中给定的字符的下一个字符的规则如下:
- 字符串中的每个字符都应当是小写元音字母 (‘a’,‘e’,‘i’,‘o’,‘u’)
- 每个元音
'a'后面都只能跟着'e' - 每个元音
'e'后面只能跟着'a'或者是'i' - 每个元音
'i'后面 不能 再跟着另一个'i' - 每个元音
'o'后面只能跟着'i'或者是'u' - 每个元音
'u'后面只能跟着'a'
以上等价于每个字符的前一个字符的规则如下:
- 每个元音
'a'前面都只能跟着'e','i','u' - 每个元音
'e'前面只能跟着'a'或者是'i' - 每个元音
'i'前面只能着'e','o' - 每个元音
'o'前面只能跟着'i' - 每个元音
'u'前面只能跟着'o','i'
我们设 dp[i][j] 代表当前长度为 i 且以字符 j 为结尾的字符串的数目,其中在此 j=0,1,2,3,4 分别代表元音字'a', 'e', 'i', 'o', 'u')。
实际计算过程中只需要保留前一个状态即可推导出后一个状态,计算长度为 iii 的状态只需要长度为 i−1 的中间变量即可,i−1 之前的状态全部都可以丢弃掉。计算过程中答案需要取模
var countVowelPermutation = function(n) {
const mod = 1000000007;
const dp = new Array(5).fill(0);
const ndp = new Array(5).fill(0);
for (let i = 0; i < 5; ++i) {
dp[i] = 1;
}
for (let i = 2; i <= n; ++i) {
/* a前面可以为e,u,i */
ndp[0] = (dp[1] + dp[2] + dp[4]) % mod;
/* e前面可以为a,i */
ndp[1] = (dp[0] + dp[2]) % mod;
/* i前面可以为e,o */
ndp[2] = (dp[1] + dp[3]) % mod;
/* o前面可以为i */
ndp[3] = dp[2];
/* u前面可以为i,o */
ndp[4] = (dp[2] + dp[3]) % mod;
dp.splice(0, 5, ...ndp);
}
let ans = 0;
for (let i = 0; i < 5; ++i) {
ans = (ans + dp[i]) % mod;
}
return ans;
};