leetcode
leetcode 2501 ~ 2550
重新排列后包含指定子字符串的字符串数目

重新排列后包含指定子字符串的字符串数目

难度:

标签:

题目描述

You are given an integer n.

A string s is called good if it contains only lowercase English characters and it is possible to rearrange the characters of s such that the new string contains "leet" as a substring.

For example:

  • The string "lteer" is good because we can rearrange it to form "leetr" .
  • "letl" is not good because we cannot rearrange it to contain "leet" as a substring.

Return the total number of good strings of length n.

Since the answer may be large, return it modulo 109 + 7.

A substring is a contiguous sequence of characters within a string.

 

 

Example 1:

Input: n = 4
Output: 12
Explanation: The 12 strings which can be rearranged to have "leet" as a substring are: "eelt", "eetl", "elet", "elte", "etel", "etle", "leet", "lete", "ltee", "teel", "tele", and "tlee".

Example 2:

Input: n = 10
Output: 83943898
Explanation: The number of strings with length 10 which can be rearranged to have "leet" as a substring is 526083947580. Hence the answer is 526083947580 % (109 + 7) = 83943898.

 

Constraints:

  • 1 <= n <= 105

代码结果

运行时间: 27 ms, 内存: 16.0 MB


/* 
题目思路:
使用 Java Stream 来简化计算过程。我们依然使用组合数学计算选择位置的方式,并且使用快速幂计算排列的方式。最后结果对 10^9 + 7 取余。
*/

import java.math.BigInteger;
import java.util.stream.LongStream;

public class GoodStringStream {
    private static final int MOD = 1000000007;

    public static void main(String[] args) {
        int n = 10;  // 示例输入
        System.out.println(countGoodStrings(n));
    }

    public static long countGoodStrings(int n) {
        if (n < 4) return 0;
        long totalWays = factorial(n);
        long invalidWays = factorial(n - 4);
        long permutationsOfLeet = factorial(4);
        long result = (totalWays * modInverse(invalidWays * permutationsOfLeet % MOD, MOD)) % MOD;
        return result;
    }

    private static long factorial(int n) {
        return LongStream.rangeClosed(1, n)
                .reduce(1, (long a, long b) -> a * b % MOD);
    }

    private static long modInverse(long a, int mod) {
        return BigInteger.valueOf(a).modInverse(BigInteger.valueOf(mod)).longValue();
    }
}

解释

方法:

此题解采用的是容斥原理来计算字符串数目。主要思路是计算出所有可能的长度为 n 的字符串数目,然后依次减去不包含 'l', 'e', 或 't' 的字符串数目,并对多次减去的部分使用容斥原理进行调整。首先计算出所有可能的字符串数目,即包含所有小写英文字母的字符串数目为 26^n。随后,分别计算出不包含 'l', 'e', 或 't' 的字符串数目,这些情况下的字符串数目分别为 25^n。对于同时不包含两个或三个特定字符的情况,根据容斥原理,需要进行加减调整,比如同时不包含 'l' 和 't' 的字符串数目为 24^n 等。最终通过加减这些数目,使用模运算确保结果在合理范围内,得到包含子字符串 'leet' 的字符串总数。

时间复杂度:

O(log n)

空间复杂度:

O(1)

代码细节讲解

🦆
为什么在计算不包含字符'e'时,使用了公式n*pow(25, n-1, mod) + pow(25, n, mod),而对于字符'l'和't'仅使用了pow(25, n, mod)?
在计算不包含字符'e'的字符串数目时,需要考虑字符串中'e'字符出现次数的不同情况,因为'e'可以出现0次或1次。公式中的pow(25, n, mod)计算的是'e'一次也不出现的情况,而n*pow(25, n-1, mod)计算的是'e'恰好出现一次的情况(e可以出现在n个位置中的任何一个,剩余位置可以是除e之外的25个字母)。相比之下,'l'和't'在题解中被认为完全不出现,因此只使用了pow(25, n, mod)来计算。
🦆
在计算同时不包含两个特定字符时,如何保证公式n*pow(24, n-1, mod) + pow(24, n, mod)能正确地计算出不包含这两个字符的字符串数目?
这个公式用于计算同时不包含两个特定字符(例如'l'和'e')的字符串数目。pow(24, n, mod)计算的是两个字符都不出现的情况。而n*pow(24, n-1, mod)计算的是其中一个字符恰好出现一次的情况,另一个字符不出现。这里的n表示该特定字符可以出现在字符串的任何一个位置,而其他位置由剩下的24个字符填充。因此,这个公式确保了在所有可能的位置上恰好出现一个特定字符的所有情况都被正确计算。
🦆
在使用容斥原理调整计数时,为什么要在最后的计算中加上不包含两个字符的情况后还要减去不包含三个字符的情况?
容斥原理用于计算涉及多个集合的总元素数。在首次减去不包含某些字符的字符串数目时(例如单独的'l', 'e', 't'),会出现对于同时不包含两个字符(例如'l'和't')的字符串的多次减除。因此,需要加回这部分数目。然而,当加回不包含两个字符的情况时,包含三个字符都不出现的情况又被多次添加,因此需要再次减去不包含三个字符的情况,以确保每种情况被正确计数。
🦆
在题解中提到的mod运算,即10^9+7,是如何保证在计算过程中不会有溢出的问题?
10^9+7是一个大质数,常用于编程中以避免整数溢出,并保持结果在一个可管理的范围内。在进行模运算时,所有的数学操作(如加法、乘法)都是在这个模下完成的,这意味着结果永远不会超过10^9+7,从而避免了大数的直接存储和操作可能引起的溢出问题。使用这种方法可以确保即使对于非常大的n,计算结果仍然是正确和可控的。

相关问题