区间动态规划例题:统计同构子字符串的数目问题
字数 1539 2025-11-02 00:38:37

区间动态规划例题:统计同构子字符串的数目问题

题目描述

给定一个字符串 s,你需要统计其中同构子字符串的数目。同构子字符串定义为:如果一个子字符串的所有字符都相同,那么它就是同构的。例如,字符串 "aaa" 有 6 个同构子字符串:"a", "a", "a", "aa", "aa", "aaa"。

注意:子字符串是字符串中连续的字符序列。即使内容相同,只要起始和结束位置不同,就被视为不同的子字符串。

解题思路

这个问题可以用简单的数学方法解决,但为了练习区间动态规划,我们将使用 DP 方法。基本思想是定义 dp[i][j] 表示子串 s[i:j+1] 是否是同构的(即所有字符相同),然后统计所有满足条件的子串。

详细解题步骤

  1. 定义状态
    定义 dp[i][j] 为一个布尔值,表示子字符串 s[i...j](包含 i 和 j)是否是同构的(即所有字符相同)。

  2. 状态转移方程
    考虑如何判断 s[i...j] 是否同构:

    • 如果 i == j(子串只有一个字符),那么它一定是同构的。
    • 如果 j > i,那么 s[i...j] 是同构的条件是:
      • s[i] == s[j](首尾字符相同)
      • 并且 s[i...j-1] 是同构的(即 dp[i][j-1] 为真)
        这是因为如果整个字符串所有字符都相同,那么去掉最后一个字符后的子串也必须所有字符相同,并且最后一个字符要与前面的字符相同。

    所以状态转移方程为:

    • 当 i == j 时:dp[i][j] = true
    • 当 j > i 时:dp[i][j] = (s[i] == s[j]) && dp[i][j-1]
  3. 初始化

    • 所有长度为 1 的子串都是同构的:dp[i][i] = true(对于所有 i)
  4. 计算顺序
    由于计算 dp[i][j] 需要用到 dp[i][j-1],我们应该按照子串长度从小到大进行计算:

    • 先计算所有长度为 1 的子串
    • 然后计算长度为 2 的子串
    • 接着计算长度为 3 的子串
    • ...
    • 最后计算长度为 n 的整个字符串
  5. 统计结果
    遍历整个 dp 表,统计所有 dp[i][j] 为 true 的个数,这就是同构子字符串的总数。

  6. 复杂度分析

    • 时间复杂度:O(n²),需要填充 n×n 的 DP 表
    • 空间复杂度:O(n²),需要存储 DP 表

示例演示

以字符串 "aab" 为例:

  1. 初始化 dp 表(3×3):

    • dp[0][0] = true ("a")
    • dp[1][1] = true ("a")
    • dp[2][2] = true ("b")
  2. 计算长度为 2 的子串:

    • dp[0][1]:s[0]='a' == s[1]='a' 且 dp[0][0]=true → true ("aa")
    • dp[1][2]:s[1]='a' ≠ s[2]='b' → false ("ab")
  3. 计算长度为 3 的子串:

    • dp[0][2]:s[0]='a' ≠ s[2]='b' → false ("aab")
  4. 统计结果:dp[0][0], dp[1][1], dp[2][2], dp[0][1] 为 true,共 4 个同构子串。

实际同构子串有:"a"(0-0), "a"(1-1), "aa"(0-1), "b"(2-2),确实为 4 个。

优化思路

实际上,这个问题有 O(n) 的简单解法:遍历字符串,统计连续相同字符的区间,对于长度为 k 的连续相同字符区间,它包含的同构子串数为 k×(k+1)/2。但 DP 方法帮助我们理解了区间DP的思维模式。

区间动态规划例题:统计同构子字符串的数目问题 题目描述 给定一个字符串 s,你需要统计其中同构子字符串的数目。同构子字符串定义为:如果一个子字符串的所有字符都相同,那么它就是同构的。例如,字符串 "aaa" 有 6 个同构子字符串:"a", "a", "a", "aa", "aa", "aaa"。 注意:子字符串是字符串中连续的字符序列。即使内容相同,只要起始和结束位置不同,就被视为不同的子字符串。 解题思路 这个问题可以用简单的数学方法解决,但为了练习区间动态规划,我们将使用 DP 方法。基本思想是定义 dp[ i][ j] 表示子串 s[ i:j+1 ] 是否是同构的(即所有字符相同),然后统计所有满足条件的子串。 详细解题步骤 定义状态 定义 dp[ i][ j] 为一个布尔值,表示子字符串 s[ i...j ](包含 i 和 j)是否是同构的(即所有字符相同)。 状态转移方程 考虑如何判断 s[ i...j ] 是否同构: 如果 i == j(子串只有一个字符),那么它一定是同构的。 如果 j > i,那么 s[ i...j ] 是同构的条件是: s[ i] == s[ j ](首尾字符相同) 并且 s[ i...j-1] 是同构的(即 dp[ i][ j-1 ] 为真) 这是因为如果整个字符串所有字符都相同,那么去掉最后一个字符后的子串也必须所有字符相同,并且最后一个字符要与前面的字符相同。 所以状态转移方程为: 当 i == j 时:dp[ i][ j ] = true 当 j > i 时:dp[ i][ j] = (s[ i] == s[ j]) && dp[ i][ j-1 ] 初始化 所有长度为 1 的子串都是同构的:dp[ i][ i ] = true(对于所有 i) 计算顺序 由于计算 dp[ i][ j] 需要用到 dp[ i][ j-1 ],我们应该按照子串长度从小到大进行计算: 先计算所有长度为 1 的子串 然后计算长度为 2 的子串 接着计算长度为 3 的子串 ... 最后计算长度为 n 的整个字符串 统计结果 遍历整个 dp 表,统计所有 dp[ i][ j ] 为 true 的个数,这就是同构子字符串的总数。 复杂度分析 时间复杂度:O(n²),需要填充 n×n 的 DP 表 空间复杂度:O(n²),需要存储 DP 表 示例演示 以字符串 "aab" 为例: 初始化 dp 表(3×3): dp[ 0][ 0 ] = true ("a") dp[ 1][ 1 ] = true ("a") dp[ 2][ 2 ] = true ("b") 计算长度为 2 的子串: dp[ 0][ 1]:s[ 0]='a' == s[ 1]='a' 且 dp[ 0][ 0 ]=true → true ("aa") dp[ 1][ 2]:s[ 1]='a' ≠ s[ 2 ]='b' → false ("ab") 计算长度为 3 的子串: dp[ 0][ 2]:s[ 0]='a' ≠ s[ 2 ]='b' → false ("aab") 统计结果:dp[ 0][ 0], dp[ 1][ 1], dp[ 2][ 2], dp[ 0][ 1 ] 为 true,共 4 个同构子串。 实际同构子串有:"a"(0-0), "a"(1-1), "aa"(0-1), "b"(2-2),确实为 4 个。 优化思路 实际上,这个问题有 O(n) 的简单解法:遍历字符串,统计连续相同字符的区间,对于长度为 k 的连续相同字符区间,它包含的同构子串数为 k×(k+1)/2。但 DP 方法帮助我们理解了区间DP的思维模式。