dp基础之难点解析K Edit Distance

小编 2026-07-01 阅读:1111 评论:0
重要的是先说三遍:先看Edit Distance,先看Edit Distance,先看Edit Distance! 本文是加强版K Edit Distance:给定N个字符串A,问哪些字符串和Target...

重要的是先说三遍:先看Edit Distance,先看Edit Distance,先看Edit Distance

本文是加强版K Edit Distance:给定N个字符串A,问哪些字符串和Target的最小编辑距离不大于K.
编辑操作和上面一样:增加(插入)、删、替换

例:
输入:A = [\"abc\",\"abd\",\"abcd\",\"adc,\"a\"](为了简单,只考虑小写字母的字符串)
Target = \"ac\"
K = 1
输出:[\"a\",\"abc\",\"adc\"]

问题分析:可以利用Edit Distance里,对每个字符串进行计算最小编辑距离,然后再把那些最小编辑距离不超过K的字符串输出来即可,但是这样会有很多冗余计算,时间会大大增加(相同前缀的字符串越多会该问题表现越明显)。比如,计算\"abc\"和\"abd\"时,两个字符穿都有相同前缀\"ab\",dp解决该问题\"ab\"的最小编辑距离要重复计算两次,实际上,我们计算一次就好了。为此,我们加入前缀树(Trie)来优化计算。

转移方程:设f[Sp][j]表示前缀Sp和Target里前j个字符(Target[0,...,j-1])的最小编辑距离,这里Sp表示A中字符串的前缀.。
下面p的父节点是q,Sq表示:Sq是Sp的少一个字符的前缀(例:Sp = \"abc\",则Sq = \"ab\"),情况和上面一致,
只不过把i用Sp代替,i-1用Sq代替。

f[Sp][j] = min{f[Sp][j-1]+1,        f[Sq][j-1]+1,             f[Sq][j]+1,f[Sq][j-1]|Sp[last] = Target[j-1]}
           min{case1:在Sp后插入Target[j-1];case2:用Target[j-1]替换Sp[last];case3:删除Sp[last]即变成Sq;case4:Sp[last]和Target最后一个相等}

初始条件:
空串和长度为L的最小编辑距离是L,空串就是root:f[Sroot][j] = f[\"\"][j] = j,(j = 0,...,n)
Sp和空串的最小编辑距离是Sp的长度:f[Sp][0] = len(Sp)

计算顺序:
    初始化f[Sroot][0]...f[Sroot][n]
    按照前缀树深度优先搜索计算每个f[Sp][0],...,f[Sp][n]

答案:f[Sp][n]<=K且Sp为给一个给定的单词的节点p的个数
    
时间复杂度:O(A中所有字符串的前缀个数*n),空间复杂度O(A中所有字符串的前缀个数*n)

代码及注释如下:

#前缀树节点结构
class TrieNode(object):
    def __init__(self):
        # 是否构成一个完成的单词,因为只有小写单词,所以把单词转到0-26之间
        self.is_word = False
        self.children = [None] * 26
        self.words = \"\"
#前缀树的类
class Trie(object):
    def __init__(self):
        self.root = TrieNode()

    def insert(self, s):
        \"\"\"insert a string called s from root.\"\"\"
        p = self.root
        n = len(s)
        for i in range(n):
            if p.children[ord(s[i]) - ord(\'a\')] is None:
                new_node = TrieNode()
                if i == n - 1: 
                    new_node.is_word = True
                    new_node.words = s
                p.children[ord(s[i]) - ord(\'a\')] = new_node
                p = new_node
            else:
                p = p.children[ord(s[i]) - ord(\'a\')]
                if i == n - 1:
                    p.is_word = True
                    p.words = s
                    return
                

class solution(object):
    def __init__(self,A,Target,K):
        self.target = Target
        self.k = K
        self.n = len(Target)
        self.res = []
        
        #init Tire
        self.trie = Trie()
        for i in range(len(A)):
            self.trie.insert(A[i])
            
        #init f[\"\"][0,...,n] 
        self.f = [i for i in range(self.n+1)]
        
    #dfs函数:在节点p,前缀Sp,f[j]:f[Sp][j]即前缀Sp转换成Target的前j个字符的最小编辑距离。
    #todo:Sp+\"a\",...,Sp+\"z\",will update :f-->nf,
    def dfs(self, p,f):
        nf  = [0 for i in range(self.n+1)]
        
        #从root节点开始看是否有A中字符串
        if p.is_word:
            #这个字符串的最小编辑距离不大于K,加入结果res里
            if f[self.n]<=self.k:
                self.res.append(p.words)
                
        #继续向下找p的子节点
        #next prefix\'s char is i in A
        for i in range(26):
            #儿子节点为空,跳过
            if p.children[i] == None:
                continue
            #特殊处理nf[0]
            #f[Sq][0] = 0
            #现在Sq-->Sp,也就是f[Sq][0]-->f[Sp][0],前缀多了一个字符,变成Target[0]
            #因为f[Sp][0] = len(Sp),现在Sp多了一个字符,只要在原来的f[0]基础上加1就可以。
            nf[0] = f[0]+1
            
            #next Target\'s char is self.target[j-1]
            for j in range(1,self.n+1):
                #case1,2,3###i:Sp-->nf  i-1:Sq-->f
                #f[Sp][j] = min{f[Sp][j-1]+1,        f[Sq][j-1]+1,             f[Sq][j]+1}
                nf[j] = min(nf[j-1]+1,f[j-1]+1,f[j]+1)
                
                #case4
                #f[Sp][j] = min{f[Sp][j],f[Sq][j-1]|Sp[last] = Target[j-1]}
                #把字符转成0-26之间
                c = ord(self.target[j-1])-ord(\"a\")
                if i == c:
                    nf[j] = min(nf[j],f[j-1])
            #寻找子节点
            self.dfs(p.children[i],nf)
        return self.res       
    #也就是从root节点开始深度遍历前缀树,并且更新每一轮的f值,将编辑距离小于K的字符串加到res里,最后返回res
   
    #dfs返回result
    def return_res(self):
        return self.dfs(self.trie.root,self.f)

A = [\"abc\",\"abd\",\"abcd\",\"adc\",\"a\"]
Target = \"ac\"
K = 1
C = solution(A,Target,K)
C.return_res()
#输出:[\'a\', \'abc\', \'adc\']

说明:可以把递归里的f直接放在TrieNode的结构里,就不用带着f在函数里递归了

版权声明

本文仅代表作者观点,不代表百度立场。
本文系作者授权百度百家发表,未经许可,不得转载。

热门文章
  • Sequential Monte Carlo Methods (SMC) 序列蒙特卡洛/粒子滤波/Bootstrap Filtering

    Sequential Monte Carlo Methods (SMC) 序列蒙特卡洛/粒子滤波/Bootstrap Filtering
    Problem Statement 我们考虑一个具有马尔可夫性质、非线性、非高斯的状态空间模型(State Space Model):对于一个时间序列上的观测结果{yt,t∈N}\\{ y_t , t \\in N \\}{yt​,t∈N},我们认为每个观测结果yty_tyt​的生成依赖于一个无法直接观察的隐变量xt∈{xt,t∈N}x_t \\in \\{x_t , t \\in N \\}xt​∈{xt​,t∈N},即:p(...
  • 机房智能化温湿度解决方式之POE供电以太网温湿度传感器

    机房智能化温湿度解决方式之POE供电以太网温湿度传感器
    机房智能化温湿度解决方式之POE供电以太网温湿度传感器 北京盈创力和电子科技有限公司 智能型TCP网口温湿度记录仪 北京IP网络温湿度记录仪厂家,北京盈创力和 北京智能型TCP网口温湿度记录仪IP网络温湿度记录仪是一种新型的基于TCP/IP协议双绞线以太网标准温湿度采集模块,利用它可以实现现场温度值、相对湿度值的采集,同时利用其自身的RJ45通信接口可以方便地和机房监控主机或交换机集线器进行联网。 工作于-40℃~85℃工业级带...
  • Hive 系统函数及示例

    Hive 系统函数及示例
    查看所有系统函数 show functions; 函数分类 内置函数【系统函数】 数学函数: floor、round、ceil、cos、log2等 字符串函数: length、reverse、trim、lower、get_json_object、repeat等 收集函数: size 转换函数: cast 日期函数: year、month、datediff、date、date_add等 条件函数: coalesce、case…w...
  • CSRF的原理和防范措施

    CSRF的原理和防范措施
    a)攻击原理:i.用户C访问正常网站A时进行登录,浏览器保存A的cookieii.用户C再访问攻击网站B,网站B上有某个隐藏的链接或者图片标签会自动请求网站A的URL地址,例如表单提交,传指定的参数iii.而攻击网站B在访问网站A的时候,浏览器会自动带上网站A的cookieiv.所以网站A在接收到请求之后可判断当前用户是登录状态,所以...
  • HTTP状态保持的原理

    HTTP状态保持的原理
    a)在用户登录之后,浏览器返回响应的时候会在响应中添加上cookieb)浏览器接收到cookie之后会自动保存c)当用户再次请求同一服务器中的其他网页的时候,浏览器会自动带上之前保存的cookied)服务接收到请求之后可以请 request 对象中取到cookie 判断当前用户是否登录  Http是无状态的,就是连接时数据互通,关闭后...
标签列表