字符串相关例题(查询子串在主串中的个数3种解法,查询最长公共子串)

子串出现的次数

题目描述

给定两个字符串 s1 和 s2 ,求 s2 在 s1 中出现的次数,字符区分大小写,已匹配的字符不计入下一次匹配

输入输出描述

输入两行字符串,分别为s1和s2,s2的长度小于等于s1

输出s2在s1中出现的次数

示例1

输入:

ABCsdABsadABCasdhjabcsaABCasd

ABC

输出:

3

示例2

输入:

AAAAAAAA

AAA

输出:

2

解题代码

1.遍历解法
def solve(s1,s2):
    if s1=="" and s2=="":
        return 1
    elif s1!="" and s2=="":
        return 0
    elif s1=="" and s2!="":
        return 0
    i=0
    count=0
    while i<len(s1):
        j=i
        k=0
        if s1[i]==s2[0]:
            j+=1
            k+=1
            while j<len(s1) and k<len(s2):
                if s1[j]==s2[k]:
                    j+=1
                    k+=1
            else:
                if k==len(s2):
                    count+=1
                    i=j
                if j==len(s1):
                    return count
        else:
            i+=1
    return count

s1=input()
s2=input()
print(solve(s1,s2))
2.滑动窗口结合字符串切片
def solve(s1,s2):
    #可以判断子串是否为空,根据题目
    if not s2:
        return "子串为空"
    #判断是否子串长于主串
    if len(s1)<len(s2):
        return "子串长于主串"
    i=0
    count=0
    while i<len(s1)-len(s2):
        if s1[i:i+len(s2)]==s2:
            count+=1
            i+=len(s2)
        else:
            i+=1
    return count

s1=input()
s2=input()
print(solve(s1,s2))
3.kmp算法
def count_substrings_kmp(s1, s2):
    # 特殊情况处理
    if not s2:
        return 0
    len1, len2 = len(s1), len(s2)
    if len2 > len1:
        return 0

    # 步骤1:构建LPS数组(最长公共前后缀数组)
    def build_lps(pattern):
        lps = [0] * len(pattern)
        length = 0  # 记录当前最长公共前后缀的长度
        i = 1
        while i < len(pattern):
            if pattern[i] == pattern[length]:
                length += 1
                lps[i] = length
                i += 1
            else:
                if length != 0:
                    # 回溯到上一个可能的匹配位置
                    length = lps[length - 1]
                else:
                    lps[i] = 0
                    i += 1
        return lps

    lps = build_lps(s2)
    count = 0
    i = 0  # i遍历s1
    j = 0  # j遍历s2

    while i < len1:
        if s1[i] == s2[j]:
            i += 1
            j += 1

        # 匹配成功一次
        if j == len2:
            count += 1
            j = 0  # 重置s2指针,寻找下一个非重叠匹配
            # 注意:此处i已自动跳过当前匹配的子串(因i在匹配时递增)

        # 匹配失败时的处理
        elif i < len1 and s1[i] != s2[j]:
            if j != 0:
                # 根据LPS数组回溯j,避免重复比较
                j = lps[j - 1]
            else:
                # j=0时直接移动i
                i += 1

    return count

# 读取输入
s1 = input().strip()
s2 = input().strip()

# 输出结果
print(count_substrings_kmp(s1, s2))

最长公共子串

题目描述

给定两个字符串 s1 和 s2 ,求 s1 与 s2 之间的最长公共子串,字符区分大小写

输入输出描述

输入两行字符串,分别为s1和s2

输出最长公共子串

示例

输入:

123ABCDEFG83hsad

iughABCDEFG23uy

输出:

ABCDEFG

求最长公共子串的最优解法是使用动态规划(Dynamic Programming),这种方法能在 O (n*m) 的时间复杂度内高效解决问题,其中 n 和 m 分别是两个字符串的长度。

算法思路

核心思想是通过一个二维数组记录两个字符串中以每个位置为结尾的最长公共子串长度:

  1. 定义 dp [i][j] 表示以 s1 [i-1] 和 s2 [j-1] 为结尾的最长公共子串长度
  2. 当 s1 [i-1] == s2 [j-1] 时,dp [i][j] = dp [i-1][j-1] + 1
  3. 当字符不匹配时,dp [i][j] = 0
  4. 同时记录最长子串的长度和结束位置,最后截取结果

最优代码实现(空间优化版)

def longest_common_substring(s1, s2):
    # 确保s1是较短的字符串,优化空间使用
    if len(s1) > len(s2):
        s1, s2 = s2, s1
    
    m, n = len(s1), len(s2)
    max_len = 0  # 最长公共子串的长度
    end_idx = 0  # 最长公共子串在s1中的结束索引
    
    # 用一维数组优化空间,只需保存上一行的状态
    dp = [0] * (m + 1)
    
    for j in range(1, n + 1):
        # 逆序更新,避免覆盖需要使用的前一个值
        prev = 0  # 保存dp[i-1][j-1]的值
        for i in range(1, m + 1):
            temp = dp[i]  # 暂存当前值,作为下一次的prev
            if s1[i-1] == s2[j-1]:
                dp[i] = prev + 1
                # 更新最长子串信息
                if dp[i] > max_len:
                    max_len = dp[i]
                    end_idx = i - 1
            else:
                dp[i] = 0
            prev = temp
    
    # 根据最长长度和结束索引截取结果
    if max_len == 0:
        return ""
    return s1[end_idx - max_len + 1 : end_idx + 1]

# 读取输入
s1 = input().strip()
s2 = input().strip()

# 计算并输出结果
print(longest_common_substring(s1, s2))

算法解析

  1. 空间优化:通过使用一维数组替代二维数组,将空间复杂度从 O (n*m) 优化到 O (min (n,m)),特别适合处理较长的字符串
  2. 逆序更新:内部循环采用逆序更新方式,避免了因覆盖而丢失上一行的必要信息
  3. 实时记录:在遍历过程中实时更新最长子串的长度和结束位置,最后一次性截取结果

复杂度分析

  • 时间复杂度:O (n*m),需要遍历两个字符串的所有可能位置
  • 空间复杂度:O (min (n,m)),只需要一个长度为较短字符串长度 + 1 的数组

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值