Leetcode 854. 相似度为 K 的字符串

发布于:2025-06-26 ⋅ 阅读:(24) ⋅ 点赞:(0)

1.题目基本信息

1.1.题目描述

对于某些非负整数 k ,如果交换 s1 中两个字母的位置恰好 k 次,能够使结果字符串等于 s2 ,则认为字符串 s1 和 s2 的 相似度为 k 。

给你两个字母异位词 s1 和 s2 ,返回 s1 和 s2 的相似度 k 的最小值。

1.2.题目地址

https://leetcode.cn/problems/k-similar-strings/description/

2.解题方法

2.1.解题思路

广度优先搜索

  • 遍历s2,比较s2[i]和s1[i],如果相等,则跳到下一位,反之,则从s1[i+1:]中找到和s2[i]相等的元素,和s1[i]进行交换,并依次搜索所有的状态,找到最小的交换次数

A*启发式搜索

深度优先搜索

2.2.解题步骤

深度优先搜索方法步骤:

  • 第一步,预处理。构建s和t数组,分别存储s1和s2中不匹配的字符

  • 第二步,构建深搜递归函数。递归任务:在指针指向i,且已经花费cost的代价(交换次数)使s[:i]和t[:i]相同的情况下;继续往后进行搜索,直到i为n;且在搜索的过程中维护s==t时的最小cost

    • 2.1.跳过s和t匹配的区域。此步必须先行,否则会报错,因为如果碰到相等元素,将导致错误的替换,造成结果被污染

    • 2.2.递归出口

    • 2.3.剪枝。如果不匹配的字符数为m,则至少需要交换(m+1)//2次(m在奇偶条件下都成立),所以如果预测的最小转换次数minSwapCnt+当前cost>=result,则无需继续搜索

    • 2.4.枚举s[i+1:]部分,进一步搜索,注意调用递归后恢复s的状态

  • 第三步,调用递归,返回结果

广度优先搜索方法步骤:

  • 第一步,构建维护变量。que维护广搜队列,元素项的模式为(s1状态,当前比较的指针位置);visited维护已经搜索过的s1字符串状态;steps维护广搜的层数;result维护相似度k的最小值

  • 第二步,广度优先搜索

    • 2.1.弹出队首节点;并将当前的s1状态与s2进行比较,观察是否可以退出

    • 2.2.跳过当前s1状态s与s2相同的字符

    • 2.3.枚举s的下一个状态,压入到队列中,并更新维护变量

A*启发式搜索方法步骤:

  • 第一步,构建计算s1状态s到s2的预估距离的函数getEstimateDist,这里通过两状态之间的至小交换次数进行表示

  • 第二步,构建获取s1的下一个状态的函数getNeigh,返回下一个状态的s1字符串列表

  • 第三步,构建维护变量。heap维护最小堆,堆中元素项的模式为(实际距离+预估距离,当前s1字符串的状态,当前遍历到的指针位置);distances各个结点从原始的s1到当前各个s1状态的交换次数;prePaths维护最短变化路径,各个s1状态的前驱状态

  • 第四步,搜索

    • 4.1.从堆中弹出"实际距离+预估距离"最小的节点;并将当前的s1状态s与s2进行比较,观察是否可以进行退出

    • 4.2.枚举s的下一个状态,将没有搜索到或者距离更近状态压入堆中,并更新维护变量

  • 第五步,最终distances[s2]即为s1到s2的最短距离

3.解题代码

深度优先搜索方法代码

from collections import deque
from heapq import heappop, heappush

class Solution:
    def kSimilarity(self, s1: str, s2: str) -> int:
        # 思路3:深度优先搜索+剪枝
        # 第一步,预处理。构建s和t数组,分别存储s1和s2中不匹配的字符
        s, t, n = [], [], 0
        for i in range(len(s1)):
            if s1[i] != s2[i]:
                s.append(s1[i])
                t.append(s2[i])
                n += 1
        if n == 0:
            return 0
        # 第二步,构建深搜递归函数。递归任务:在指针指向i,且已经花费cost的代价(交换次数)使s[:i]和t[:i]相同的情况下;继续往后进行搜索,直到i为n;且在搜索的过程中维护s==t时的最小cost
        self.result = n - 1
        def dfs(i:int, cost:int) -> None:
            # 2.1.跳过s和t匹配的区域。此步必须先行,否则会报错,因为如果碰到相等元素,将导致错误的替换,造成结果被污染
            while i < n and s[i] == t[i]:
                i += 1
            # 2.2.递归出口
            if i == n:
                self.result = min(self.result, cost)
                return
            if cost > self.result:
                return
            # 2.3.剪枝。如果不匹配的字符数为m,则至少需要交换(m+1)//2次(m在奇偶条件下都成立),所以如果预测的最小转换次数minSwapCnt+当前cost>=result,则无需继续搜索
            minSwapCnt = (sum(s[a] != t[a] for a in range(i, n)) + 1) // 2
            if minSwapCnt + cost >= self.result:
                return
            # 2.4.枚举s[i+1:]部分,进一步搜索,注意调用递归后恢复s的状态
            for j in range(i + 1, n):
                if s[j] == t[i]:
                    s[i], s[j] = s[j], s[i]
                    dfs(i + 1, cost + 1)
                    s[i], s[j] = s[j], s[i]
        # 第三步,调用递归,返回结果
        dfs(0, 0)
        return self.result

广度优先搜索方法代码

from collections import deque
from heapq import heappop, heappush

class Solution:
    def kSimilarity(self, s1: str, s2: str) -> int:
        # 思路1:广度优先搜索。遍历s2,比较s2[i]和s1[i],如果相等,则跳到下一位,反之,则从s1[i+1:]中找到和s2[i]相等的元素,和s1[i]进行交换,并依次搜索所有的状态,找到最小的交换次数
        n = len(s1)
        # 第一步,构建维护变量。que维护广搜队列,元素项的模式为(s1状态,当前比较的指针位置);visited维护已经搜索过的s1字符串状态;steps维护广搜的层数;result维护相似度k的最小值
        que = deque([(s1, 0)])
        visited = set([s1])
        steps = 0
        result = inf
        # 第二步,广度优先搜索
        while que:
            for _ in range(len(que)):
                # 2.1.弹出队首节点;并将当前的s1状态与s2进行比较,观察是否可以退出
                s, i = que.popleft()
                if s == s2:
                    return steps
                # 2.2.跳过当前s1状态s与s2相同的字符
                while i < n and s[i] == s2[i]:
                    i += 1
                # 2.3.枚举s的下一个状态,压入到队列中,并更新维护变量
                for j in range(i, n):
                    if s2[i] == s[j]:
                        arr = list(s)
                        arr[i], arr[j] = arr[j], arr[i]
                        nextS = "".join(arr)
                        if nextS not in visited:
                            que.append((nextS, i + 1))
                            visited.add(nextS)
            steps += 1

A*启发式搜索方法代码

from collections import deque
from heapq import heappop, heappush

class Solution:
    def kSimilarity(self, s1: str, s2: str) -> int:
        # 思路2:A*启发式算法
        n = len(s1)
        # 第一步,构建计算s1状态s到s2的预估距离的函数getEstimateDist,这里通过两状态之间的至小交换次数进行表示
        def getEstimateDist(s:str) -> int:
            cnt = 0
            for i in range(len(s2)):
                if s2[i] != s[i]:
                    cnt += 1
            return cnt // 2 + 1
        # 第二步,构建获取s1的下一个状态的函数getNeigh,返回下一个状态的s1字符串列表
        def getNeigh(node):
            dist, s, i = node
            while s[i] == s2[i]:
                i += 1
            arr = list(s)
            ans = []
            j = i
            while j < n:
                if s[j] == s2[i]:
                    arr[i], arr[j] = arr[j], arr[i]
                    nextS = "".join(arr)
                    ans.append(nextS)
                    arr[i], arr[j] = arr[j], arr[i]
                j += 1
            return ans
        # 第三步,构建维护变量。heap维护最小堆,堆中元素项的模式为(实际距离+预估距离,当前s1字符串的状态,当前遍历到的指针位置);distances各个结点从原始的s1到当前各个s1状态的交换次数;prePaths维护最短变化路径,各个s1状态的前驱状态
        heap = [(getEstimateDist(s1), s1, 0)]
        distances = {s1: 0}
        prePaths = {s1: None}
        # 第四步,搜索
        while heap:
            # 4.1.从堆中弹出"实际距离+预估距离"最小的节点;并将当前的s1状态s与s2进行比较,观察是否可以进行退出
            addedDist, s, i = heappop(heap)
            if s == s2:
                return distances[s2]
            # 4.2.枚举s的下一个状态,将没有搜索到或者距离更近状态压入堆中,并更新维护变量
            for s3 in getNeigh((addedDist, s, i)):
                newDist = distances[s] + 1
                if s3 not in distances or distances[s3] > newDist:
                    distances[s3] = newDist
                    prePaths[s3] = s
                    heappush(heap, (newDist + getEstimateDist(s3), s3, i + 1))
        # 第五步,最终distances[s2]即为s1到s2的最短距离
        return distances[s2]

4.执行结果