[搜索算法系列] —— 双向搜索

字串变换问题

已知两个字串a、b,两个数组from、to,变换规则为:from[i]可以转换为to[i]。问a到b需要经过多少次变换?若10步内a无法变换到b,输出'NO ANSWER!'。

例如对于a = 'abcd',b = 'xyz',from = ['abc', 'ud', 'y'],to = ['xu', 'y', 'yz']'abcd' -> 'xud' -> 'xy' -> 'xyz'需要经历3次变换。

该题的解决方案使用到了广度优先搜索,我们将经过一次变换能够得到的所有字符串作为一种状态,然后判断是否存在目标字符串,不存在则计算下一层状态,存在则当前层数为最小变换次数。

可以得到如下代码。

def solution(a, b, f, t):
    def extend(h, q):
        s, n = h
        for i in range(len(s)):
            for j in range(len(f)):
                if f[j] != s[i:i+len(f[j])]:
                    continue
                q.append([s[:i] + t[j] + s[i+len(f[j]):], n+1])
    queue = [[a, 0]]
    while queue:
        head = queue.pop(0)
        if head[0] == b: return head[1]
        extend(head, queue)
    return 'NO ANSWER!'

如果该题目没有时间限制,这样写当然没有问题。

考虑一个字符串的一次变换的所有可能,与字符串的长度和变换规则有关,这个中间状态可能数量非常庞大,那我们如何对该代码进行优化呢?

如果你阅读过我的上一篇文章可能会想到通过拆分问题规模来分别求解,以达到降低问题数量级的目的。而在本题中,我们分别从a和b同时开始搜索,如果它们的搜索路径相遇了,即找到最小变换次数,通过双向搜索的方式,我们将规模为2N的问题,分解为了两个规模为N的问题。

def solution(a, b, f, t):
    ans = float('inf')
    qa, qb, da, db = [a], [b], {a:0}, {b:0}
    level = 0

    def extend(q, d, f, t):
        s = q.pop(0)
        for i in range(len(s)):
            for j in range(len(f)):
                if f[j] != s[i:i+len(f[j])]:
                    continue
                tmp = s[:i] + t[j] + s[i+len(f[j]):]
                q.append(tmp)
                d[tmp] = d[s]+1

    while qa and qb and level <= 10:
        while qa and da[qa[0]] == level:
            extend(qa, da, f, t)
        while qb and db[qb[0]] == level:
            extend(qb, db, t, f)
        level += 1
        for k in da.keys():
            ans = min(ans, da[k] + db.get(k, float('inf')))
        if ans < float('inf'): return ans
    return 'NO ANSWER!'

该段代码中,开辟了两个队列和两个字典,分别存储双向搜索状态和字串所在层数。每一次队列扩展整一层的数据,用level来记录当前层数。扩展完成后判断两个字典是否有公共键值,如果有则说明发现了搜索路径的交点,对应层数相加即转换次数。

本文示例题目与acwing 190.字串变换一致,读者可自行尝试提交,验证自己代码的正确性。

你可能感兴趣的:(算法)