【LetMeFly】1641.统计字典序元音字符串的数目

力扣题目链接:https://leetcode.cn/problems/count-sorted-vowel-strings/

给你一个整数 n,请返回长度为 n 、仅由元音 (a, e, i, o, u) 组成且按 字典序排列 的字符串数量。

字符串 s 按 字典序排列 需要满足:对于所有有效的 i,s[i] 在字母表中的位置总是与 s[i+1] 相同或在 s[i+1] 之前。

 

示例 1:

输入:n = 1

输出:5

解释:仅由元音组成的 5 个字典序字符串为 ["a","e","i","o","u"]

示例 2:

输入:n = 2

输出:15

解释:仅由元音组成的 15 个字典序字符串为

["aa","ae","ai","ao","au","ee","ei","eo","eu","ii","io","iu","oo","ou","uu"]

注意,"ea" 不是符合题意的字符串,因为 'e' 在字母表中的位置比 'a' 靠后

示例 3:

输入:n = 33

输出:66045

 

提示:

1 <= n <= 50 

方法一.1:动态规划

我们一位一位地生成字符串,先生成长度为1的字符串“a、e、i、o、u”,再生成长度为2的字符串“aa、ae、…”,再生成长度为3的字符串,…,最终生成长度为n的字符串。

我们使用dp[i]代表生成当前长度字符串时,以第

i

+

1

i+1

i+1个元音字母结尾的字符串的个数。

为什么要以结尾作为dp的转移条件呢?因为长度为

k

k

k的字符串是在长度为

k

1

k-1

k−1的字符串的基础上生成的(

k

=

0

k=0

k=0时除外),生成的标准是,新添加上去的这一个字符,不小于之前的最后一个字符。只有这样,才能保证字符串为字典序。

假如长度为

k

1

k-1

k−1的字符串的最后一个字符为

a

a

a,那么生成长度为

k

k

k的字符串时,我们可以在上一个字符串最后的

a

a

a的基础上,添加“a或e或i或o或u”;假如长度

k

1

k-1

k−1的字符串的最后一个字符为

e

e

e,那么生成长度为

k

k

k的字符串时,我们可以在上一个字符串最后的

a

e

ae

ae的基础上,添加“e或i或o或u”;…

也就是说,新增的字符(假设是第j+1个元音)可以增加在

k

1

k-1

k−1长的字符串的 所有不大于j+1结尾的字符 的后面

for (int i = 2; i <= n; i++) { // 生成长度为2的字符串

int newDP[5] = {0, 0, 0, 0, 0};

for (int j = 0; j < 5; j++) { // 求新的dp[j](newDP[j])

for (int k = 0; k <= j; k++) { // 之前结尾字符不超过j的都可以

newDP[j] += dp[k];

}

}

swap(dp, newDP); // 用新的dp覆盖之前的dp

}

时间复杂度

O

(

n

×

C

2

)

O(n \times C^2)

O(n×C2),其中

C

C

C是元音音符的个数

C

=

5

C=5

C=5空间复杂度

O

(

C

)

O(C)

O(C)

AC代码

C++

class Solution {

public:

int countVowelStrings(int n) {

int dp[5] = {1, 1, 1, 1, 1};

for (int i = 2; i <= n; i++) {

int newDp[5] = {0, 0, 0, 0, 0};

for (int j = 0; j < 5; j++) {

for (int i = 0; i <= j; i++) {

newDp[j] += dp[i];

}

}

swap(dp, newDp);

}

return dp[0] + dp[1] + dp[2] + dp[3] + dp[4];

}

};

Python

class Solution:

def countVowelStrings(self, n: int) -> int:

dp = [1 for _ in range(5)]

for i in range(2, n + 1):

newDp = [0 for _ in range(5)]

for j in range(5):

for k in range(0, j + 1):

newDp[j] += dp[k]

dp = newDp

return sum(dp)

方法一.2:动态规划

有没有办法在之前的基础上进行优化呢?

不难发现,

n

e

w

D

P

[

j

]

=

k

=

0

j

d

p

[

k

]

newDP[j] = \sum_{k=0}^{j} dp[k]

newDP[j]=∑k=0j​dp[k]

比如

n

e

w

D

P

[

2

]

=

d

p

[

0

]

+

d

p

[

1

]

+

d

p

[

2

]

newDP[2] = dp[0] + dp[1] + dp[2]

newDP[2]=dp[0]+dp[1]+dp[2]

n

e

w

D

P

[

3

]

=

d

p

[

0

]

+

d

p

[

1

]

+

d

p

[

2

]

+

d

p

[

3

]

newDP[3] = dp[0] + dp[1] + dp[2] + dp[3]

newDP[3]=dp[0]+dp[1]+dp[2]+dp[3]

所以有

n

e

w

D

P

[

j

]

=

n

e

w

D

P

[

j

1

]

+

d

p

[

j

]

newDP[j] = newDP[j - 1] + dp[j]

newDP[j]=newDP[j−1]+dp[j]

也就是说,新的dp可以在前一个dp结果的基础上,由

O

(

1

)

O(1)

O(1)的时间复杂度算出。

这样就优化了一层循环(虽然常数

C

C

C仅仅为5)

for (int i = 2; i <= n; i++) { // 生成长度为2的字符串

int newDP[5] = {dp[0], 0, 0, 0, 0};

for (int j = 1; j < 5; j++) { // 这里要从1开始

newDP[j] = newDP[j - 1] + dp[j];

}

swap(dp, newDP); // 用新的dp覆盖之前的dp

}

不难发现,我们可以将空间也进行一丢丢的优化:

for (int i = 2; i <= n; i++) { // 生成长度为2的字符串

for (int j = 1; j < 5; j++) { // 这里要从1开始

dp[j] = dp[j - 1] + dp[j];

}

}

时间复杂度

O

(

n

×

C

)

O(n \times C)

O(n×C),其中

C

C

C是元音音符的个数

C

=

5

C=5

C=5,这里由原来的

C

2

C^2

C2减小为了

C

C

C空间复杂度

O

(

C

)

O(C)

O(C)

AC代码

C++

class Solution {

public:

int countVowelStrings(int n) {

int dp[5] = {1, 1, 1, 1, 1};

for (int i = 2; i <= n; i++) {

for (int j = 1; j < 5; j++) {

dp[j] += dp[j - 1];

}

}

return accumulate(dp, dp + 5, 0);

}

};

Python

class Solution:

def countVowelStrings(self, n: int) -> int:

dp = [1 for _ in range(5)]

for i in range(2, n + 1):

for j in range(1, 5):

dp[j] += dp[j - 1]

return sum(dp)

方法二:数学 之 排列组合

方法二完全不同于方法一,方法二采用数学思想,将问题巧妙地转化为排列组合问题。

现在我们换个角度:

从左到右有5个盒子,盒子的名称分别为a、e、i、o、u

现在有n个小球放入到这5个盒子中,盒子允许为空(允许一些盒子不放小球)

小球全部放入盒子中之后,从左到右拿出小球,取出哪个盒子中的小球,就往字符串中添加哪个盒子的名称。

诶?这不就生成长度为n的字典序字符串了吗?

因此问题就转化为: n个相同的小球放入5个盒子,允许某些盒子为空,有多少种放法。

首先我们考虑不允许盒子为空的情况:

将n个小球放入5个盒子,每个盒子中至少有一个小球,一共有多少种放法呢?

我们只需要拿4个隔板,在n个小球之间的

n

1

n-1

n−1个间隔中,插入这4个隔板。这样,小球就被分成了5份。将每一份分别放入对应的盒子中即可达成目标。

n

1

n-1

n−1个间隔中放入4个隔板,有多少种放法?很简单,组合数

C

n

1

4

C_{n-1}^4

Cn−14​即为答案。

接下来我们考虑盒子允许为空的情况。

我们需要明白:n个小球放入5个盒子盒子能空 等价于

n

+

5

n+5

n+5个小球放入5个盒子盒子不空

这是为什么呢?

n

+

5

n+5

n+5个小球放入5个盒子,每个盒子不能为空,那么我们可以先将其中的5个小球分别放入5个盒子中,剩下的n个小球随意放入5个盒子中(这样剩下的n个小球就可以随意放置,不用考虑是否哪个盒子中没有放小球了)

n

+

5

n+5

n+5个小球放入5个盒子且盒子不空,方法为

C

n

+

5

1

4

=

C

n

+

4

4

=

(

n

+

4

)

×

(

n

+

3

)

×

(

n

+

2

)

×

(

n

+

1

)

4

×

3

×

2

×

1

C_{n+5-1}^4=C_{n+4}^4=\frac{(n+4)\times(n+3)\times(n+2)\times(n+1)}{4\times3\times2\times1}

Cn+5−14​=Cn+44​=4×3×2×1(n+4)×(n+3)×(n+2)×(n+1)​,这,不就是我们要求的结果吗?

时间复杂度

O

(

C

)

O(C)

O(C),其中

C

C

C是元音音符的个数

C

=

5

C=5

C=5,完全可以立即为

O

(

1

)

O(1)

O(1)空间复杂度

O

(

1

)

O(1)

O(1)

AC代码

C++

// n小球放入5个盒子中,允许为空

// C_{n - 1}^{4}为n小球放入5盒子且不空的情况,C_{n + 4}^{4}为n + 5小球放入5盒子且不空的情况(n + 5个小球放入5个盒子且不空,相当于先将多出的5个虚拟小球放入盒子中,其余n个小球随便放,可空)

class Solution {

public:

int countVowelStrings(int n) {

return (n + 4) * (n + 3) * (n + 2) * (n + 1) / 24;

}

};

Python

# from math import comb

class Solution:

def countVowelStrings(self, n: int) -> int:

return comb(n + 4, 4)

同步发文于CSDN,原创不易,转载请附上原文链接哦~ Tisfy:https://letmefly.blog.csdn.net/article/details/129834519

查看原文