线性动态规划:最长重复子串(动态规划解法)
字数 1484 2025-11-21 22:01:52

线性动态规划:最长重复子串(动态规划解法)

题目描述

给定一个字符串s,找出其中最长的重复子串的长度。"重复子串"指的是在该字符串中至少出现两次(可以有重叠部分,但不能是完全相同的两个子串位置)的子串。

例如:

  • 输入:"banana",输出:3(最长重复子串"ana")
  • 输入:"abcd",输出:0(没有重复子串)

解题思路

这个问题可以用动态规划来解决,核心思想是:

  • 定义dp[i][j]表示以第i个字符结尾的子串和以第j个字符结尾的子串的最长公共后缀长度
  • 通过比较字符的相等性来递推这个长度
  • 最终找到所有公共后缀长度中的最大值

详细步骤

步骤1:定义状态

定义二维数组dp[i][j],表示:

  • 以字符串s中第i个字符结尾的子串
  • 和以第j个字符结尾的子串
  • 的最长公共后缀的长度

注意:这里i和j表示字符在字符串中的位置索引(从0开始)。

步骤2:状态转移方程

对于每个位置对(i,j),其中i < j:

  • 如果s[i] == s[j](即两个位置的字符相同):
    • 当i > 0时,dp[i][j] = dp[i-1][j-1] + 1
    • 当i == 0时,dp[i][j] = 1(因为前面没有字符了)
  • 如果s[i] != s[j]:
    • dp[i][j] = 0

关键点:我们只考虑i < j的情况,避免同一个子串与自己比较。

步骤3:初始化

  • 创建一个大小为n×n的二维数组dp,初始值全为0
  • 其中n是字符串s的长度

步骤4:填充dp表

我们按行和列遍历整个dp表:

  • 外层循环:j从0到n-1
  • 内层循环:i从0到j-1(确保i < j)

对于每个(i,j)对:

  • 如果s[i] == s[j]:
    • 如果i > 0:dp[i][j] = dp[i-1][j-1] + 1
    • 如果i == 0:dp[i][j] = 1
  • 否则:dp[i][j] = 0

步骤5:寻找结果

在填充dp表的过程中,我们记录遇到的最大值:

  • result = max(result, dp[i][j])

这个最大值就是最长重复子串的长度。

举例说明

以字符串"banana"为例:

字符串:b a n a n a
索引: 0 1 2 3 4 5

我们构建dp表的部分关键值:

当j=3(字符'a')时:

  • i=1(字符'a'):s[1]=='a' == s[3]=='a',且i>0,dp[1][3] = dp[0][2] + 1 = 0 + 1 = 1
  • i=3时跳过(i < j不满足)

当j=4(字符'n')时:

  • i=2(字符'n'):s[2]=='n' == s[4]=='n',且i>0,dp[2][4] = dp[1][3] + 1 = 1 + 1 = 2

当j=5(字符'a')时:

  • i=1(字符'a'):s[1]=='a' == s[5]=='a',且i>0,dp[1][5] = dp[0][4] + 1 = 0 + 1 = 1
  • i=3(字符'a'):s[3]=='a' == s[5]=='a',且i>0,dp[3][5] = dp[2][4] + 1 = 2 + 1 = 3

最终找到的最大值是3,对应子串"ana"。

算法复杂度

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

代码实现(伪代码)

function longestRepeatingSubstring(s):
    n = length(s)
    dp = 创建n×n的二维数组,初始化为0
    result = 0
    
    for j from 0 to n-1:
        for i from 0 to j-1:
            if s[i] == s[j]:
                if i > 0:
                    dp[i][j] = dp[i-1][j-1] + 1
                else:
                    dp[i][j] = 1
                result = max(result, dp[i][j])
            else:
                dp[i][j] = 0
                
    return result

这个解法通过动态规划巧妙地找到了最长的重复子串,核心在于利用公共后缀长度的递推关系来避免重复计算。

线性动态规划:最长重复子串(动态规划解法) 题目描述 给定一个字符串s,找出其中最长的重复子串的长度。"重复子串"指的是在该字符串中至少出现两次(可以有重叠部分,但不能是完全相同的两个子串位置)的子串。 例如: 输入:"banana",输出:3(最长重复子串"ana") 输入:"abcd",输出:0(没有重复子串) 解题思路 这个问题可以用动态规划来解决,核心思想是: 定义dp[ i][ j ]表示以第i个字符结尾的子串和以第j个字符结尾的子串的最长公共后缀长度 通过比较字符的相等性来递推这个长度 最终找到所有公共后缀长度中的最大值 详细步骤 步骤1:定义状态 定义二维数组dp[ i][ j ],表示: 以字符串s中第i个字符结尾的子串 和以第j个字符结尾的子串 的最长公共后缀的长度 注意:这里i和j表示字符在字符串中的位置索引(从0开始)。 步骤2:状态转移方程 对于每个位置对(i,j),其中i < j: 如果s[ i] == s[ j ](即两个位置的字符相同): 当i > 0时,dp[ i][ j] = dp[ i-1][ j-1 ] + 1 当i == 0时,dp[ i][ j ] = 1(因为前面没有字符了) 如果s[ i] != s[ j ]: dp[ i][ j ] = 0 关键点:我们只考虑i < j的情况,避免同一个子串与自己比较。 步骤3:初始化 创建一个大小为n×n的二维数组dp,初始值全为0 其中n是字符串s的长度 步骤4:填充dp表 我们按行和列遍历整个dp表: 外层循环:j从0到n-1 内层循环:i从0到j-1(确保i < j) 对于每个(i,j)对: 如果s[ i] == s[ j ]: 如果i > 0:dp[ i][ j] = dp[ i-1][ j-1 ] + 1 如果i == 0:dp[ i][ j ] = 1 否则:dp[ i][ j ] = 0 步骤5:寻找结果 在填充dp表的过程中,我们记录遇到的最大值: result = max(result, dp[ i][ j ]) 这个最大值就是最长重复子串的长度。 举例说明 以字符串"banana"为例: 字符串:b a n a n a 索引: 0 1 2 3 4 5 我们构建dp表的部分关键值: 当j=3(字符'a')时: i=1(字符'a'):s[ 1]=='a' == s[ 3]=='a',且i>0,dp[ 1][ 3] = dp[ 0][ 2 ] + 1 = 0 + 1 = 1 i=3时跳过(i < j不满足) 当j=4(字符'n')时: i=2(字符'n'):s[ 2]=='n' == s[ 4]=='n',且i>0,dp[ 2][ 4] = dp[ 1][ 3 ] + 1 = 1 + 1 = 2 当j=5(字符'a')时: i=1(字符'a'):s[ 1]=='a' == s[ 5]=='a',且i>0,dp[ 1][ 5] = dp[ 0][ 4 ] + 1 = 0 + 1 = 1 i=3(字符'a'):s[ 3]=='a' == s[ 5]=='a',且i>0,dp[ 3][ 5] = dp[ 2][ 4 ] + 1 = 2 + 1 = 3 最终找到的最大值是3,对应子串"ana"。 算法复杂度 时间复杂度:O(n²),需要填充n×n的dp表 空间复杂度:O(n²),需要存储整个dp表 代码实现(伪代码) 这个解法通过动态规划巧妙地找到了最长的重复子串,核心在于利用公共后缀长度的递推关系来避免重复计算。