【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

你可能感兴趣的:(LeetCode,算法,leetcode,c++)