LeetCode 1641. 统计字典序元音字符串的数目

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

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

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

字符串 s字典序排列 需要满足:对于所有有效的 is[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 k1的字符串的基础上生成的( k = 0 k=0 k=0时除外),生成的标准是,新添加上去的这一个字符,不小于之前的最后一个字符。只有这样,才能保证字符串为字典序。

  • 假如长度为 k − 1 k-1 k1的字符串的最后一个字符为 a a a,那么生成长度为 k k k的字符串时,我们可以在上一个字符串最后的 a a a的基础上,添加“a或e或i或o或u”;
  • 假如长度 k − 1 k-1 k1的字符串的最后一个字符为 e e e,那么生成长度为 k k k的字符串时,我们可以在上一个字符串最后的 a e ae ae的基础上,添加“e或i或o或u”;

也就是说,新增的字符(假设是第j+1个元音)可以增加在 k − 1 k-1 k1长的字符串的 所有不大于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=0jdp[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[j1]+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 n1个间隔中,插入这4个隔板。这样,小球就被分成了5份。将每一份分别放入对应的盒子中即可达成目标。

n − 1 n-1 n1个间隔中放入4个隔板,有多少种放法?很简单,组合数 C n − 1 4 C_{n-1}^4 Cn14即为答案。

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

我们需要明白: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+514=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

猜你喜欢

转载自blog.csdn.net/Tisfy/article/details/129834519
今日推荐