leetcode
leetcode 2151 ~ 2200
完美分割的方案数

完美分割的方案数

难度:

标签:

题目描述

You are given a string s that consists of the digits '1' to '9' and two integers k and minLength.

A partition of s is called beautiful if:

  • s is partitioned into k non-intersecting substrings.
  • Each substring has a length of at least minLength.
  • Each substring starts with a prime digit and ends with a non-prime digit. Prime digits are '2', '3', '5', and '7', and the rest of the digits are non-prime.

Return the number of beautiful partitions of s. Since the answer may be very large, return it modulo 109 + 7.

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

 

Example 1:

Input: s = "23542185131", k = 3, minLength = 2
Output: 3
Explanation: There exists three ways to create a beautiful partition:
"2354 | 218 | 5131"
"2354 | 21851 | 31"
"2354218 | 51 | 31"

Example 2:

Input: s = "23542185131", k = 3, minLength = 3
Output: 1
Explanation: There exists one way to create a beautiful partition: "2354 | 218 | 5131".

Example 3:

Input: s = "3312958", k = 3, minLength = 1
Output: 1
Explanation: There exists one way to create a beautiful partition: "331 | 29 | 58".

 

Constraints:

  • 1 <= k, minLength <= s.length <= 1000
  • s consists of the digits '1' to '9'.

代码结果

运行时间: 250 ms, 内存: 16.2 MB


/*
题目思路:
1. 和上面的思路相同,只是这里我们使用 Java Stream 来实现。
2. 使用 stream 来处理字符串和动态规划数组。
*/

import java.util.*;
import java.util.stream.IntStream;

public class PerfectPartitionStream {
    private static final int MOD = 1000000007;
    private static final Set<Character> PRIME_DIGITS = new HashSet<>(Arrays.asList('2', '3', '5', '7'));
    private static final Set<Character> NON_PRIME_DIGITS = new HashSet<>(Arrays.asList('1', '4', '6', '8', '9'));

    public int perfectPartition(String s, int k, int minLength) {
        int n = s.length();
        int[] dp = new int[n + 1];
        dp[0] = 1;

        IntStream.rangeClosed(1, n).forEach(i -> {
            IntStream.rangeClosed(i - minLength, 0).filter(j -> i - j <= minLength * k && j >= 0)
                .forEach(j -> {
                    if (PRIME_DIGITS.contains(s.charAt(j)) && NON_PRIME_DIGITS.contains(s.charAt(i - 1))) {
                        dp[i] = (dp[i] + dp[j]) % MOD;
                    }
                });
        });

        return dp[n];
    }

    public static void main(String[] args) {
        PerfectPartitionStream solution = new PerfectPartitionStream();
        System.out.println(solution.perfectPartition("23542185131", 3, 2)); // 3
        System.out.println(solution.perfectPartition("23542185131", 3, 3)); // 1
        System.out.println(solution.perfectPartition("3312958", 3, 1)); // 1
    }
}

解释

方法:

本题目的解法使用了动态规划。首先,创建一个布尔数组`pri`来标记哪些数字是质数。接着,将字符串`s`转换为整数数组方便处理。然后,创建一个`split_allowed`数组用来标记在哪些位置可以进行分割,即前一个字符是非质数并且后一个字符是质数的位置。接下来,使用一个动态规划数组`dp`,其中`dp[j]`表示从字符串开始到位置`j`的完美分割数。`sum_last`数组用来存储累加和,以便在计算中快速获取区间和。对于每段可能的分割,更新`dp`和`sum_cur`数组,并最终返回最后的分割方案数。

时间复杂度:

O(kn)

空间复杂度:

O(n)

代码细节讲解

🦆
为什么在检查是否可以作为分割点时,只考虑前一个字符是非质数和后一个字符是质数的情况?是否还需要考虑字符长度的限制?
在此问题中,分割点的选择基于特定的质数规则,即前一个字符(分割点前的字符)必须是非质数,后一个字符(分割点后的字符)必须是质数。这种规则是为了确保每个分割出来的子字符串在特定的质数条件下开始和结束。至于字符长度的限制,这确实是一个重要的考虑因素,但在确定分割点是否允许时,长度限制并未直接涉及。字符长度的限制在动态规划的具体实现中考虑,通过控制从哪些位置可以开始新的子段,以确保每个子段至少有 `minLength` 长度。
🦆
在处理字符串首尾字符不符合条件的情况时,为什么直接返回0?是否有可能存在其他有效的分割方法?
如果字符串的首字符是质数或尾字符是非质数,则整个字符串无法在题目定义的规则下完美分割,因为所有的分割都必须确保每个子字符串从非质数开始到质数结束。由于首尾字符设置了整个字符串的起始和结束条件,如果这些条件不满足,那么无法形成任何有效的分割,因此直接返回0是合理的。在这种情况下,不存在其他有效的分割方法。
🦆
在动态规划实现中,`dp[j]`是如何确保每个子字符串长度至少为`minLength`?
在动态规划的实现中,`dp[j]`表示从字符串开始到位置`j`的完美分割数。为了确保每个子字符串长度至少为`minLength`,代码中的循环从`i * minLength`开始,这保证了每次计算`dp[j]`时,都至少从`minLength`长度之前的索引开始考虑,即确保了分割出来的每个子字符串长度至少为`minLength`。此外,计算`dp[j]`时,使用的是`sum_last[j - minLength]`,这也进一步确保了长度限制的遵守。
🦆
在更新`sum_cur`数组时,为什么选择累加`dp[j]`值而不是直接使用`dp[j]`的当前值?
在更新`sum_cur`数组时,选择累加`dp[j]`值是为了维护一个到当前位置为止的所有可能的分割方案的总和。这样做可以便于下一次计算新的`dp[j]`值时,直接利用这个累积和,无需重新遍历之前的元素来求和。这是一种优化手段,使得每次更新`dp[j]`时的计算更加高效,只需要O(1)的时间即可得到所需的前缀和。

相关问题