SWE · Coding

碎片字符串查首个子串:跨片匹配并返回片内下标

KMP状态不中断,匹配可从一片开始在下一片结束。

题目重点:KMP状态不中断,匹配可从一片开始在下一片结束。

题意与假设

这道练习题保留了面经中值得训练的核心约束,并将需要澄清的条件单独列出。现场拿到变体时,先和面试官确认这些条件。

  • fragments保持顺序,视为拼接文本;返回第一个匹配起点的(fragmentIndex,offset),None表示无匹配。
  • 按code point计offset;空pattern训练返回(0,0)即逻辑起点,即使无片,调用方可另定空输入语义。

解题路线

  1. 预处理pattern失败函数,遍历各片字符时保留matched,不因换片归零。失配用prefix回退,不重新从头扫。
  2. 完成匹配时全局起点=当前全局位置−pattern长度+1;片前缀长度数组与bisect_right将起点映回真实字符所属片。
  3. 空片产生重复前缀长度,bisect_right跳过零长片,保证非空匹配的起点落在含字符的片内。纯流不保留全部片时可记录最近pattern长度的来源信息。

手动走一遍

["a","bcd","ea","de"]查de:第一次在bcd最后一个d与ea首e跨片匹配,返回(1,2)。["","ab","","c"]查bc返回(1,1),不是空片下标。

Python 参考实现

下面的代码按上文假设实现,适合本地运行并追加自己的测试用例。

代码区可横向滚动;使用键盘时,先聚焦代码区再按方向键查看。

from bisect import bisect_right


def find_fragment(fragments,pattern):
    if not pattern:return (0,0)
    prefix=[0]*len(pattern)
    for i in range(1,len(pattern)):
        j=prefix[i-1]
        while j and pattern[i]!=pattern[j]:j=prefix[j-1]
        if pattern[i]==pattern[j]:j+=1
        prefix[i]=j
    offsets=[0]
    for fragment in fragments:offsets.append(offsets[-1]+len(fragment))
    matched=position=0
    for fragment in fragments:
        for c in fragment:
            while matched and c!=pattern[matched]:matched=prefix[matched-1]
            if c==pattern[matched]:matched+=1
            if matched==len(pattern):
                start=position-len(pattern)+1
                i=bisect_right(offsets,start)-1
                return i,start-offsets[i]
            position+=1
    return None

复杂度

总扫描字符N、pattern长M、片数F;O(N+M+F)预处理/扫描,最后一次回映O(logF),空间O(M+F)。不拼接整个文本。

易错点与追问

容易失分的边界

  • 按每片调用find会漏掉跨片匹配。
  • 找最后一次出现或每片最早再比较不是首个全局匹配。

继续深挖

  • 要全部重叠匹配,完成后matched应回退到哪?
  • 返回起止两片位置并支持真正无界流,需要存多少来源状态?
✓已复制微信号!