leetcode
leetcode 2101 ~ 2150
对字母串可执行的最大删除数

对字母串可执行的最大删除数

难度:

标签:

题目描述

代码结果

运行时间: 3642 ms, 内存: 317.5 MB


/*
 * 题目思路:
 * - 这里我们使用 Java Stream 来简化代码。
 * - 使用 Stream 的 map 和 reduce 操作来替代常规的循环操作。
 * - 思路和前面的解法类似,我们仍然是计算 dp 数组。
 */

import java.util.stream.IntStream;

public int deleteString(String s) {
    int n = s.length();
    int[] dp = new int[n + 1];
    IntStream.range(0, n).forEach(i -> dp[i] = 1);
    IntStream.range(0, n).map(i -> n - 1 - i).forEach(i -> 
        IntStream.range(1, (n - i) / 2 + 1).forEach(k -> {
            if (s.substring(i, i + k).equals(s.substring(i + k, i + 2 * k))) {
                dp[i] = Math.max(dp[i], 1 + dp[i + k]);
            }
        })
    );
    return dp[0];
}

解释

方法:

此题解采用动态规划的方法解决问题。定义一个数组 f,其中 f[i] 表示从字符串的第 i 个字符开始到结束所有可能的删除操作的最大数目。具体过程中,先计算字符串任意两个后缀的最长公共前缀(LCP),存储在二维数组 lcp 中。然后使用这个 lcp 数组来快速判断任意两个相等长度的子串是否相等。对于每个位置 i,检查所有可能的 j(长度为从 1 到 (n-i)/2 的子串),如果 s[i:i+j] 等于 s[i+j:i+2j],则尝试更新 f[i] 为 f[i+j]+1 的最大值。最终,f[0] 即为整个字符串 s 的最大删除次数。

时间复杂度:

O(n^2)

空间复杂度:

O(n^2)

代码细节讲解

🦆
在动态规划解法中,你是如何确定f数组的初始值以及更新规则的?
在动态规划解法中,f数组的初始值被设为0,这意味着每个位置至少可以删除一次(即它自身)。更新规则基于判断能否删除连续重复的子串。当发现某个子串可以被重复且连续删除时(即子串相等),f[i]会更新为f[i+j]加上当前这一次删除操作。这样,f[i]代表从位置i开始,所能执行的最大删除操作数。
🦆
为什么在构建最长公共前缀(LCP)数组时选择从后向前计算,而不是从前向后?
从后向前计算LCP数组可以有效利用已知的LCP值来简化计算。当我们知道s[i+1]到s[j+1]的LCP时,可以轻易推出s[i]到s[j]的LCP,只需检查s[i]是否等于s[j]。这种方法减少了重复的比较和计算,提高了效率。
🦆
在计算最长公共前缀(LCP)时,如何处理边界条件,特别是当i或j接近字符串末尾时?
当i或j接近字符串末尾时,LCP的计算需要确保不会越界。因此,在初始化LCP数组时,所有的边界值(特别是当i或j等于字符串长度n时)都被设为0。这是因为超出字符串长度的部分没有字符,所以公共前缀长度为零。在LCP的递推计算中,只有当s[i]和s[j]相等且它们的位置都在字符串长度内时,才会基于s[i+1]和s[j+1]的LCP值进行更新。
🦆
在对f数组进行更新时,你是如何利用lcp数组来判断两个子串是否相等的?具体的判断条件是什么?
在更新f数组时,利用lcp数组来快速判断两个子串是否相等。具体的判断条件是检查lcp[i][i+j]是否大于等于j。这表示s[i:i+j]与s[i+j:i+2j]的最长公共前缀长度至少为j,即这两个子串完全相同。因此,如果这个条件成立,我们可以认为从i开始的子串可以执行一次删除操作(删除s[i:i+j]),然后从i+j位置继续尝试删除。

相关问题