leetcode
leetcode 2351 ~ 2400
将字符串分割为最少的美丽子字符串

将字符串分割为最少的美丽子字符串

难度:

标签:

题目描述

Given a binary string s, partition the string into one or more substrings such that each substring is beautiful.

A string is beautiful if:

  • It doesn't contain leading zeros.
  • It's the binary representation of a number that is a power of 5.

Return the minimum number of substrings in such partition. If it is impossible to partition the string s into beautiful substrings, return -1.

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

 

Example 1:

Input: s = "1011"
Output: 2
Explanation: We can paritition the given string into ["101", "1"].
- The string "101" does not contain leading zeros and is the binary representation of integer 51 = 5.
- The string "1" does not contain leading zeros and is the binary representation of integer 50 = 1.
It can be shown that 2 is the minimum number of beautiful substrings that s can be partitioned into.

Example 2:

Input: s = "111"
Output: 3
Explanation: We can paritition the given string into ["1", "1", "1"].
- The string "1" does not contain leading zeros and is the binary representation of integer 50 = 1.
It can be shown that 3 is the minimum number of beautiful substrings that s can be partitioned into.

Example 3:

Input: s = "0"
Output: -1
Explanation: We can not partition the given string into beautiful substrings.

 

Constraints:

  • 1 <= s.length <= 15
  • s[i] is either '0' or '1'.

代码结果

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


/*
 * 题目思路:
 * 1. 使用Java Stream API来进行动态规划求解。
 * 2. 遍历字符串的所有子串,判断是否为美丽字符串。
 * 3. 使用一个Boolean数组记录某个子串是否为美丽字符串,然后利用IntStream更新dp数组。
 */
import java.util.stream.IntStream;
public class BeautifulSubstringStream {
    public static int minBeautifulSubstrings(String s) {
        int n = s.length();
        int[] dp = new int[n + 1];
        Arrays.fill(dp, Integer.MAX_VALUE);
        dp[0] = 0;
        Boolean[] isBeautiful = new Boolean[n + 1];
        Arrays.fill(isBeautiful, false);
        for (int i = 1; i <= n; i++) {
            for (int j = i - 1; j >= 0; j--) {
                String sub = s.substring(j, i);
                isBeautiful[i] = isBeautiful(sub);
            }
        }
        IntStream.rangeClosed(1, n).forEach(i -> {
            if (isBeautiful[i]) {
                IntStream.range(0, i).forEach(j -> {
                    if (dp[j] != Integer.MAX_VALUE) {
                        dp[i] = Math.min(dp[i], dp[j] + 1);
                    }
                });
            }
        });
        return dp[n] == Integer.MAX_VALUE ? -1 : dp[n];
    }
    private static boolean isBeautiful(String s) {
        if (s.charAt(0) == '0') return false;
        long num = Long.parseLong(s, 2);
        while (num % 5 == 0) num /= 5;
        return num == 1;
    }
}

解释

方法:

此题解采用深度优先搜索(DFS)与记忆化搜索的方法来解决问题。首先,计算出所有可能的美丽子字符串,即5的幂次的二进制表示,由于s的最大长度为15,计算到5^6即可覆盖所有情况。然后,使用递归函数`dfs`来尝试所有可能的子字符串分割方式,每次尝试匹配已知的美丽子字符串,并递归地处理剩余的字符串部分。如果当前字符串的首字符是'0',则直接返回无穷大(表示不可能分割成美丽子字符串)。如果能匹配到一个美丽子字符串,就在此基础上继续处理,寻找最小的分割次数。最后,如果得到的结果是无穷大,则说明无法将字符串分割成美丽子字符串,返回-1,否则返回分割的最小次数。

时间复杂度:

O(7^n)

空间复杂度:

O(n)

代码细节讲解

🦆
为何题解中选择使用深度优先搜索(DFS)而不是动态规划(DP)或贪心算法来解决此问题?
题解选择使用深度优先搜索(DFS)主要是因为问题的本质是在寻找所有可能的分割方式,并从中选择最优解,这自然适合使用回溯法来探索所有可能的子字符串组合。相比之下,动态规划虽然也可以解决此问题,但是其空间复杂度可能较高,因为需要存储每一个子问题的解。而贪心算法不适用于此问题,因为局部最优选择并不一定能导致全局最优解。DFS加上记忆化可以有效地减少重复计算,提高效率。
🦆
题解中提到的美丽子字符串是'5的幂次的二进制表示',但是这是否意味着所有这些子字符串都不包含前导0?请确认。
是的,美丽子字符串定义为'5的幂次的二进制表示',由于二进制表示中5的幂次(例如1, 5, 25, 125等)转换为二进制后自然不会有前导0。因此,所有这些美丽子字符串都不包含前导0。
🦆
题解提到,如果首字符是'0',则返回无穷大。这种情况是否意味着字符串中间的零字符不会影响结果,只有首字符是零时才特殊处理?
不完全是这样。题解中特别提到首字符为'0'时返回无穷大,是因为任何有效的美丽子字符串不以'0'开头。如果在分割的过程中遇到任何以'0'开头的子字符串段,这种分割方式都是无效的。因此,不仅是首字符,任何分割后子字符串的首字符若为'0',都会导致该分割方式被视为无效。
🦆
题解中使用了记忆化来优化递归,记忆化是如何实现的,它是基于什么条件或键来存储结果的?
在题解中,记忆化是通过装饰器`@cache`来实现的,这通常是Python中functools模块提供的一个功能,用于自动存储函数的输入和对应的输出结果。在这个场景中,`dfs`函数递归处理字符串,每次调用都基于当前的子字符串`s`,因此,记忆化是基于子字符串`s`作为键来存储每个子字符串的最小分割次数。这样可以避免重复计算相同字符串的分割结果,极大提高了效率。

相关问题