【854. 相似度为 K 的字符串】

来源:力扣(LeetCode)

描述:

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

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

示例 1:

输入:s1 = "ab", s2 = "ba"
输出:1

示例 2:

输入:s1 = "abc", s2 = "bca"
输出:2

提示:

  • 1 <= s1.length <= 20

  • s2.length == s1.length

  • s1 和 s2 只包含集合 {‘a’, ‘b’, ‘c’, ‘d’, ‘e’, ‘f’} 中的小写字母

  • s2 是 s1 的一个字母异位词

方法一:广度优先搜索

  由于题目中给定的字符串的长度范围为 [1, 20] 且只包含 6 种不同的字符,因此我们可以枚举所有可能的交换方案,在搜索时进行减枝从而提高搜索效率,最终找到最小的交换次数。

  设字符串的长度为 n,如果当前第 i 个字符满足 s1[i] != s2[i],则从 s1[i+1,⋯] 选择一个合适的字符 s1[j] 进行交换,其中满足 s1[j] = s2[i], j ∈ [ i + 1, n − 1]。每次我们进行交换时,可将字符串 s1 的前 x 个字符通过交换使得 s1[0, ⋯, x − 1] = s2[0, ⋯, x − 1],最终使得 s1 的所有字符与 s2 相等即可。我们通过以上变换,找到最小的交换次数使得 s1 与 s2 相等。

在搜索时,我们需要进行减枝,我们设当前的通过交换后的字符串 s’1 为一个中间状态,用哈希表记录这些中间状态,当通过交换时发现当前状态已经计算过,则此时我们可以直接跳过该状态。

代码:

class Solution {
public:
    int kSimilarity(string s1, string s2) {
        int n = s1.size();
        queue<pair<string, int>> qu;
        unordered_set<string> visit;
        qu.emplace(s1, 0);
        visit.emplace(s1);
        for (int step = 0;; step++) {
            int sz = qu.size();
            for (int i = 0; i < sz; i++) {
                auto [cur, pos] = qu.front();
                qu.pop();
                if (cur == s2) {
                    return step;
                }
                while (pos < n && cur[pos] == s2[pos]) {
                    pos++;
                }
                for (int j = pos + 1; j < n; j++) {
                    if (cur[j] != s2[j] && cur[j] == s2[pos]) { // 剪枝,只在 cur[j] != s2[j] 时去交换
                        swap(cur[pos], cur[j]);
                        if (!visit.count(cur)) {
                            visit.emplace(cur);
                            qu.emplace(cur, pos + 1);
                        }
                        swap(cur[pos], cur[j]);
                    }
                }
            }
        }
    }
};

执行用时:60 ms, 在所有 C++ 提交中击败了52.61%的用户
内存消耗:24.6 MB, 在所有 C++ 提交中击败了38.81%的用户
author:LeetCode-Solution

方法二:A* 算法(进阶)

A* 搜索算法(A* 读作 A-star),简称 A* 算法,是一种在图形平面上,对于有多个节点的路径求出最低通过成本的算法。它属于图遍历和最佳优先搜索算法(英文:Best-first search),亦是 BFS 的改进。

A* 算法主要步骤如下:

  1. 将方法一中的 BFS 队列转换为优先队列(小根堆);

  2. 队列中的每个元素为 (dist[s] + f(s), s),dist[s] 表示从初始状态 s1 到当前状态 s 的距离,f(s) 表示从当前状态 ss 到目标状态 s2 的估计距离,这两个距离之和作为堆排序的依据;

  3. 当终点第一次出队时,说明找到了从起点 s1 到终点 s2 的最短路径,直接返回对应的距离;

  4. f(s) 是估价函数,并且估价函数要满足 f(s) <= g(s),其中 g(s) 表示 s 到终点 s2 的真实距离;

需要注意的是,A* 算法只能保证终点第一次出队时,即找到了一条从起点到终点的最小路径,不能保证其他点出队时也是从起点到当前点的最短路径。

代码:

using pis = pair<int, string>;

class Solution {
public:
    int kSimilarity(string s1, string s2) {
        priority_queue<pis, vector<pis>, greater<pis>> q;
        q.push({f(s1, s2), s1});
        unordered_map<string, int> dist;
        dist[s1] = 0;
        while (1) {
            auto [_, s] = q.top();
            q.pop();
            if (s == s2) {
                return dist[s];
            }
            for (auto& nxt : next(s, s2)) {
                if (!dist.count(nxt) || dist[nxt] > dist[s] + 1) {
                    dist[nxt] = dist[s] + 1;
                    q.push({dist[nxt] + f(nxt, s2), nxt});
                }
            }
        }
    }

    int f(string& s, string& s2) {
        int cnt = 0;
        for (int i = 0; i < s.size(); ++i) {
            cnt += s[i] != s2[i];
        }
        return (cnt + 1) >> 1;
    }

    vector<string> next(string& s, string& s2) {
        int i = 0, n = s.size();
        for (; s[i] == s2[i]; ++i) {}
        vector<string> res;
        for (int j = i + 1; j < n; ++j) {
            if (s[j] == s2[i] && s[j] != s2[j]) {
                swap(s[i], s[j]);
                res.push_back(s);
                swap(s[i], s[j]);
            }
        }
        return res;
    }
};

执行用时:12 ms, 在所有 C++ 提交中击败了87.31%的用户
内存消耗:10.6 MB, 在所有 C++ 提交中击败了62.69%的用户
author:lcbin

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
Levenshtein Distance算法是一种常见的字符串相似度算法,也被称为编辑距离算法。其主要思想是通过计算两个字符串之间的编辑距离来确定它们的相似程度。 编辑距离指的是将一个字符串转换成另一个字符串所需的最少操作次数,其中每次操作可以是插入、删除或替换一个字符。例如,将字符串“kitten”转换成字符串“sitting”需要进行3次操作,即将“k”替换为“s”,将“e”替换为“i”,将“n”替换为“g”。 Levenshtein Distance算法的实现一般使用动态规划的方法,通过填充一个二维矩阵来计算两个字符串之间的编辑距离。具体实现过程可以参考以下伪代码: ``` function LevenshteinDistance(s1, s2): m = length(s1) n = length(s2) d = new matrix(m+1, n+1) for i from 0 to m: d[i, 0] = i for j from 0 to n: d[0, j] = j for j from 1 to n: for i from 1 to m: if s1[i] == s2[j]: cost = 0 else: cost = 1 d[i, j] = min(d[i-1, j]+1, d[i, j-1]+1, d[i-1, j-1]+cost) return d[m, n] ``` 在以上代码中,变量s1和s2分别表示两个待比较的字符串,m和n分别表示它们的长度,矩阵d用于存储编辑距离的计算结果。首先,将矩阵d的第一行和第一列分别初始化为0到n和0到m的整数。然后,对于每个(i, j)位置,如果s1[i]等于s2[j],则将cost设为0,否则设为1。最后,根据递推公式d[i, j] = min(d[i-1, j]+1, d[i, j-1]+1, d[i-1, j-1]+cost)来填充矩阵d,并返回d[m, n]作为编辑距离的结果。 Levenshtein Distance算法的时间复杂度为O(m*n),其中m和n分别为两个字符串的长度。在实际应用中,该算法可用于拼写检查、数据去重等场景。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

千北@

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值