来源:力扣(LeetCode)
描述:
对于某些非负整数 k
,如果交换 s1
中两个字母的位置恰好 k
次,能够使结果字符串等于 s2
,则认为字符串 s1
和 s2
的 相似度为 k
。
给你两个字母异位词 s1
和 s2
,返回 s1
和 s2
的相似度 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-star),简称 A* 算法,是一种在图形平面上,对于有多个节点的路径求出最低通过成本的算法。它属于图遍历和最佳优先搜索算法(英文:Best-first search),亦是 BFS 的改进。
A* 算法主要步骤如下:
将方法一中的 BFS 队列转换为优先队列(小根堆);
队列中的每个元素为 (dist[s] + f(s), s),dist[s] 表示从初始状态 s1 到当前状态 s 的距离,f(s) 表示从当前状态 ss 到目标状态 s2 的估计距离,这两个距离之和作为堆排序的依据;
当终点第一次出队时,说明找到了从起点 s1 到终点 s2 的最短路径,直接返回对应的距离;
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