likes
comments
collection
share

854. 相似度为 K 的字符串 : 常规搜索运用题

作者站长头像
站长
· 阅读数 11

我报名参加金石计划一期挑战——瓜分10万奖池,这是我的第19篇文章,点击查看活动详情

题目描述

这是 LeetCode 上的 854. 相似度为 K 的字符串 ,难度为 困难

Tag : 「启发式搜索」、「AStar 算法」

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

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

示例 1:

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

输出:1

示例 2:

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

输出:2

提示:

  • 1<=s1.length<=201 <= s1.length <= 201<=s1.length<=20
  • s2.length=s1.lengths2.length = s1.lengths2.length=s1.length
  • s1 和 s2  只包含集合 {'a', 'b', 'c', 'd', 'e', 'f'} 中的小写字母
  • s2s1 的一个字母异位词

AStar 算法

问题本质为将 s1 转换为 s2 的最小操作次数,由于题目确保了 s1s2 互为字母异位词(必然有解),因此最好的求解方式是使用 AStar 算法。

可直接根据本题规则来设计 AStar 的启发式函数: 对于两个状态 ab 直接计算出「理论最小转换次数」: 不同字符串的转换成本之和,由于每一次交换最多可减少两个不同的字符,我们可计算 ab 的不同字符数量 ansansans,对应的理论最小转换次数为 ⌊ans+12⌋\left \lfloor \frac{ans + 1}{2} \right \rfloor2ans+1

需要注意的是:由于我们衡量某个字符 str 的估值是以目标字符串 target 为基准,因此我们只能确保 target 出队时为「距离最短」,而不能确保中间节点出队时「距离最短」,因此我们不能单纯根据某个节点是否「曾经入队」而决定是否入队,还要结合当前节点的「最小距离」是否被更新而决定是否入队。

一些细节:在使用当前状态(字符串)poll 拓展新状态(字符串)nstr 时,只拓展能够减少不同字符数量的方案,从而收窄搜索空间。

Java 代码:

class Solution {
    int n;
    String t;
    int f(String s) {
        int ans = 0;
        for (int i = 0; i < n; i++) ans += s.charAt(i) != t.charAt(i) ? 1 : 0;
        return ans + 1 >> 1;
    }
    public int kSimilarity(String s1, String s2) {
        if (s1.equals(s2)) return 0;
        t = s2;
        n = s1.length();
        Map<String, Integer> map = new HashMap<>();
        PriorityQueue<String> pq = new PriorityQueue<>((a,b)->{
            int v1 = f(a), v2 = f(b), d1 = map.get(a), d2 = map.get(b);
            return (v1 + d1) - (v2 + d2);
        });
        map.put(s1, 0);
        pq.add(s1);
        while (!pq.isEmpty()) {
            String poll = pq.poll();
            int step = map.get(poll);
            char[] cs = poll.toCharArray();
            int idx = 0;
            while (idx < n && cs[idx] == t.charAt(idx)) idx++;
            for (int i = idx + 1; i < n; i++) {
                if (cs[i] == t.charAt(idx) && cs[i] != t.charAt(i)) {
                    swap(cs, idx, i);
                    String nstr = String.valueOf(cs);
                    if (map.containsKey(nstr) && map.get(nstr) <= step + 1) continue;
                    if (nstr.equals(t)) return step + 1;
                    map.put(nstr, step + 1);
                    pq.add(nstr);
                    swap(cs, idx, i);
                }
            }
        }
        return -1; // never
    }
    void swap(char[] cs, int i, int j) {
        char c = cs[i];
        cs[i] = cs[j];
        cs[j] = c;
    }
}
  • 时间复杂度:启发式搜索不分析时空复杂度
  • 空间复杂度:启发式搜索不分析时空复杂度

最后

这是我们「刷穿 LeetCode」系列文章的第 No.854 篇,系列开始于 2021/01/01,截止于起始日 LeetCode 上共有 1916 道题目,部分是有锁题,我们将先把所有不带锁的题目刷完。

在这个系列文章里面,除了讲解解题思路以外,还会尽可能给出最为简洁的代码。如果涉及通解还会相应的代码模板。

为了方便各位同学能够电脑上进行调试和提交代码,我建立了相关的仓库:github.com/SharingSour…

在仓库地址里,你可以看到系列文章的题解链接、系列文章的相应代码、LeetCode 原题链接和其他优选题解。

更多更全更热门的「笔试/面试」相关资料可访问排版精美的 合集新基地 🎉🎉