BM65-最长公共子序列(二)

该问题通过动态规划解决,定义dp[i][j]表示处理到s1的第i个元素和s2的第j个元素时的最长公共子序列。初始状态为当i或j为0时,dp[i][j]为空字符串。状态转移根据s1和s2当前字符是否相等,相等则在前一状态基础上加当前字符,否则取之前两状态中较长的公共子序列。最后返回dp[len1][len2],若为空则返回-1。

题目

给定两个字符串str1和str2,输出两个字符串的最长公共子序列。如果最长公共子序列为空,则返回"-1"。目前给出的数据,仅仅会存在一个最长的公共子序列。

数据范围:0≤∣str1∣,∣str2∣≤2000。

要求:空间复杂度 O(n^2),时间复杂度 O(n^2)。

示例1

输入:"1A2C3D4B56","B1D23A456A"

返回值:"123456"

示例2

输入:"abc","def"

返回值:"-1"

示例3

输入:"abc","abc"

返回值:"abc"

示例4

输入:"ab",""

返回值:"-1"


思路

  • 需要明确状态:当前处理到的 s1 和 s2 分别的第 i 和第 j 个字符。
  • 定义状态数组:dp[i][j]表示从左到右,当处理到s1的第i个元素和s2的第j个元素时的公共子序列。
  • 状态初始化:即当i==0或j==0的情况,dp[i][j]为"",因为空字符串没有公共子序列。
  • 状态转移:
    • 当前字符相等,则添加结果,i 和 j 指针右移,状态转移方程为:dp[i][j] = dp[i-1][j-1] + s1.charAt(i-1)
    • 当前字符不相等,则还需要分两种情况,取长度较长的情况,状态转移方程为: dp[i][j] = dp[i-1][j].length() > dp[i][j-1].length() ? dp[i-1][j] : dp[i][j-1]

代码

import java.util.*;

public class Solution {
    /**
     * longest common subsequence
     * @param s1 string
     * @param s2 string
     * @return string
     */
    public String LCS (String s1, String s2) {
        //子序列:不连续,但相对顺序不能发生改变
        //dp[i][j]表示s1的[0,i]区间以及s2的[0,j]区间内,长度最长的公共子序列

        //1.创建dp表
        int len1 = s1.length(), len2 = s2.length();
        String[][] dp = new String[len1 + 1][len2 + 1];

        //2.初始化
        //当i==0或j==0的情况,dp[i][j]为"",因为空字符串没有公共子序列
        for (int i = 0; i <= len1; i++) {
            // j == 0
            dp[i][0] = "";
        }
        for (int j = 0; j <= len2; j++) {
            // i == 0
            dp[0][j] = "";
        }

        //3.填表
        for (int i = 1; i <= len1; i++) {
            for (int j = 1; j <= len2; j++) {
                if (s1.charAt(i - 1) == s2.charAt(j - 1)) { // 当前字符相等,则添加结果
                    dp[i][j] = dp[i - 1][j - 1] + s1.charAt(i - 1);
                } else { // 当前字符不相等,则还需要分两种情况,取长度较长的情况
                    dp[i][j] = dp[i - 1][j].length() > dp[i][j - 1].length() ? dp[i - 1][j] : dp[i][j - 1];
                }
            }
        }

        //4.返回值
        return dp[len1][len2] == "" ? "-1" : dp[len1][len2];
    }
}

 

<think>好的,用户现在想了解BM算法的实现原理。之前我回答过SGBM算法的实现原理,现在需要转向BM算法。首先,我需要回忆BM算法的基本概念和核心思想。BM算法全称是Boyer-Moore算法,是一种高效的字符串匹配算法,通常用于单模式匹配,比如在文本编辑器中查找字符串。 用户提供的引用内容中提到了BM算法比KMP算法性能更好,通常在实践中有3-5倍的提升。这让我想到需要比较BM和KMP的不同之处。用户可能已经了解KMP,所以需要从KMP的基础出发,解释BM的优势。 接下来,我需要确定BM算法的两个核心规则:坏字符规则和好后缀规则。这两个规则是BM算法的关键,能够实现跳跃式匹配,从而减少比较次数。需要详细解释这两个规则的工作原理,以及它们如何配合使用。 用户提供的引用中提到了BM算法从模式串的尾部开始匹配,这与传统的从头开始的匹配方法不同。这可能导致用户对匹配顺序有疑问,需要解释这种逆向匹配的好处,比如更早地发现不匹配的情况,从而更快地移动模式串。 另外,引用里还提到BM算法在最坏情况下有O(n)的时间复杂度,但平均情况下表现优异。需要说明这种时间复杂度的来源,以及为什么在实际应用中效率更高。 用户可能对具体实现步骤感兴趣,比如如何构建坏字符表和好后缀表。需要分步骤解释,并举例说明。例如,坏字符表的构建方法是记录每个字符在模式串中最后出现的位置,而好后缀表则需要处理模式串中的后缀子串。 考虑到用户可能希望看到代码示例,我需要提供一个简单的Python实现,展示BM算法的基本结构,包括两个规则的实现和应用。同时,需要指出实际应用中可能的优化点,比如结合两种规则的最大移动距离。 用户的问题中还提到了BM算法在suricata中的应用,可能对实际应用场景感兴趣。需要简要提及BM算法在网络安全或文本处理中的实际案例,增强回答的实用性。 最后,需要生成相关问题,帮助用户进一步学习。例如,比较BM和KMP的区别,BM的优化方法,或者BM在实际情况中的局限性。这些问题应该围绕用户可能的需求展开,促进深入理解。 总结来说,回答需要结构清晰,分步骤解释BM算法的原理,结合示例和代码,比较与其他算法的差异,并解答用户可能的疑问点,确保内容准确且易于理解。</think>### BM算法实现原理详解 BM(Boyer-Moore)算法是一种高效的单模式字符串匹配算法,**核心思想是通过逆向匹配和跳跃式移动模式串**,将时间复杂度降低到最坏情况 $O(n)$,平均性能优于KMP算法[^1]。其实现基于两大规则:**坏字符规则**和**好后缀规则**。 --- #### 一、算法核心思想 1. **逆向匹配** 从模式串的**末尾字符**开始向前匹配(传统算法从头部开始),通过尽早发现不匹配的字符,减少无效比较次数。 2. **跳跃式移动** 根据匹配失败时的字符信息,计算模式串可跳过的最大安全距离,避免逐字符移动。 --- #### 、关键规则解析 ##### 1. 坏字符规则(Bad Character Rule) - **触发条件**:文本中某个字符与模式串不匹配时,该字符称为**坏字符**。 - **移动策略**: 1. 若坏字符存在于模式串中,移动模式串使模式串中**最右端的坏字符**对齐文本中的坏字符位置。 2. 若坏字符不存在于模式串中,直接移动模式串到坏字符的下一位。 - **数学表示**: $$ \text{移动距离} = \max(1, \text{坏字符在模式串中最右位置} - \text{坏字符在模式串中的当前索引}) $$ - **示例**: ``` 文本: A B C D E F G 模式: E F G 匹配到字符D时,D不在模式串中,移动距离=3(模式串长度) ``` ##### 2. 好后缀规则(Good Suffix Rule) - **触发条件**:存在已匹配的后缀子串(称为“好后缀”)。 - **移动策略**: 1. 若模式串中存在与好后缀相同的子串,移动模式串使其与好后缀对齐。 2. 若不存在,移动模式串到好后缀的后一位。 - **数学表示**: $$ \text{移动距离} = \text{模式串长度} - \text{好后缀在模式串中的最右出现位置} - 1 $$ - **示例**: ``` 文本: A B C A B D A B C 模式: A B C 匹配到D时,好后缀"BC"在模式串首部出现,移动距离=2 ``` --- #### 三、预处理阶段 1. **坏字符表(Bad Character Table)** - 记录每个字符在模式串中**最后出现的位置**。 - 构建方法: ```python def build_bc_table(pattern): bc_table = {} for i in range(len(pattern)): bc_table[pattern[i]] = i # 覆盖重复字符的最后位置 return bc_table ``` 2. **好后缀表(Good Suffix Table)** - 记录每个后缀子串在模式串中的**最右匹配位置**。 - 构建方法(以模式串"ABCAB"为例): - 后缀"B" → 在位置1出现 - 后缀"AB" → 在位置0出现 - 最终移动距离通过最长公共前缀计算。 --- #### 四、算法实现步骤 1. **初始化**:构建坏字符表和好后缀表。 2. **匹配过程**: ```python def boyer_moore(text, pattern): bc_table = build_bc_table(pattern) gs_table = build_gs_table(pattern) # 需实现好后缀表逻辑 n, m = len(text), len(pattern) i = 0 # 文本对齐位置 while i <= n - m: j = m - 1 # 从模式串末尾开始匹配 while j >= 0 and text[i+j] == pattern[j]: j -= 1 if j < 0: return i # 匹配成功 else: # 计算两种规则的移动距离,取最大值 bc_shift = j - bc_table.get(text[i+j], -1) gs_shift = gs_table[j] i += max(bc_shift, gs_shift) return -1 ``` --- #### 五、性能分析 | 规则 | 优势场景 | 时间复杂度 | 空间复杂度 | |--------------|-------------------------|--------------------|------------------| | 坏字符规则 | 字符集较大(如英文) | $O(n/m)$(最佳) | $O(k)$(k为字符集大小) | | 好后缀规则 | 模式串有重复子串 | $O(n)$(最坏) | $O(m)$ | --- #### 六、BM与KMP对比 | 特性 | BM算法 | KMP算法 | |--------------|-------------------------|---------------------| | 匹配方向 | 从右向左 | 从左向右 | | 跳跃效率 | 更高(双重规则) | 中等(仅失败函数) | | 预处理时间 | $O(m + k)$ | $O(m)$ | | 适用场景 | 字符集较大的文本搜索 | 小字符集或流式数据 | --- #### 七、实际应用 - **网络安全**:suricata使用BM算法实现高效流量特征匹配[^1]。 - **文本编辑器**:大规模文档的快速搜索。 - **生物信息学**:DNA序列模式匹配。 ```python # 简化的BM算法实现(仅坏字符规则) def boyer_moore_simple(text, pattern): bc_table = {char: idx for idx, char in enumerate(pattern)} n, m = len(text), len(pattern) i = 0 while i <= n - m: j = m - 1 while j >= 0 and text[i+j] == pattern[j]: j -= 1 if j == -1: return i else: bad_char = text[i+j] # 计算移动距离:坏字符在模式中的位置 or -1 shift = j - bc_table.get(bad_char, -1) i += max(1, shift) return -1 ``` --- ### 相关问题 1. **BM算法如何处理模式串中重复字符的情况?** 2. **为什么坏字符规则在某些场景下会导致负移动?如何避免?** 3. **BM3D算法BM字符串匹配算法有何本质区别?**[^3][^4] 4. **如何优化BM算法以支持Unicode等大字符集?**
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值