【2022-09-21每日一题】854. 相似度为 K 的字符串[Hard]

2022-09-21
4分钟阅读时长

2022-09-21每日一题:854. 相似度为 K 的字符串

难度:Hard

标签:广度优先搜索 、 字符串

对于某些非负整数 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'} 中的小写字母
  • s2s1 的一个字母异位词

方法一:广度优先搜索

详细思路过程见官方题解,这里只做个人刷题记录,方便后续查询阅读

func kSimilarity(s1 string, s2 string) (step int) {
    type pair struct {
        s string
        i int
    }
    q := []pair{{s1, 0}}
    vis := map[string]bool{s1: true} // 避免重复搜索
    for n := len(s1); ; step++ {
        tmp := q
        q = nil
        for _, p := range tmp {
            s, i := p.s, p.i
            if s == s2 { // 搜索终止,返回最少次数
                return step
            }
            // 跳过相同字母
            for i < n && s[i] == s2[i] {
                i++
            }
            t := []byte(s)
            for j := i+1; j < n; j++ {
                // 找相同字母,且对应位置字母不同
                if s[j] == s2[i] && s[j] != s2[j] {
                    t[i], t[j] = t[j], t[i]
                    if t := string(t); !vis[t] {
                        vis[t] = true
                        q = append(q, pair{t, i+1})
                    }
                    // 恢复
                    t[i], t[j] = t[j], t[i]
                }
            }
        }
    }
    return step
}

方法二:深度优先搜索

func kSimilarity(s1 string, s2 string) int {
    var s, t []byte
    // 搜索不同的位置
    for i := 0; i < len(s1); i++ {
        if s1[i] != s2[i] {
            s, t = append(s, s1[i]), append(t, s2[i])
        }
    }
    // s1 == s2 情况,直接返回
    n := len(s)
    if n == 0 {
        return 0
    }
    minSwap := func(i int) int {
        diff := 0
        for j := i; j < n; j++ {
            if s[j] != t[j] {
                diff++
            }
        }
        return (diff+1) >> 1 // 相当于除2
    }
    ans := n - 1
    var dfs func (int, int) 
    dfs = func(i, cost int) {
        // 剪枝
        if cost >= ans {
            return
        }
        // 跳过相同位置
        for i < n && s[i] == t[i] {
            i++
        }
        if i == n {
            ans = min(ans, cost)
            return
        }
        // 当前状态的交换次数下限大于等于当前的最小交换次数
        if cost+minSwap(i) >= ans {
            return
        }
        for j := i+1; j < n; j++ {
            // todo: 此处官方没有 s[j] != t[j], 可以移除
            if s[j] == t[i] && s[j] != t[j] {
                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 ans
}

func min(a, b int) int {
    if a < b {
        return a
    }
    return b
}

方法三:A* 启发式搜索

func kSimilarity(s1 string, s2 string) int {
    n := len(s1)
    pq := PriorityQueue{}
    heap.Push(&pq, State{0, 0, 0, s1})
    visit := map[string]bool{s1: true}
    for pq.Len() > 0 {
        state := heap.Pop(&pq).(State)
        cost, pos, s := state.cost, state.pos, state.s
        if s == s2 {
            return state.cost
        }
        for pos < n && s[pos] == s2[pos] {
            pos++
        }
        t := []byte(s)
        for j := pos+1; j < n; j++ {
            if s[j] == s2[pos] && s[j] != s2[j] {
                t[pos], t[j] = t[j], t[pos]
                if t := string(t); !visit[t] {
                    visit[t] = true
                    heap.Push(&pq, State{cost+1+minSwap(s2, t, pos+1), cost+1, pos+1, t})
                }
                t[pos], t[j] = t[j], t[pos]
            }
        }
    }
    return 0
}

func minSwap(s1, s2 string, i int) int {
    diff := 0
    for j := i; j < len(s1); j++ {
        if s1[j] != s2[j] {
            diff++
        }
    }
    return (diff+1)>>1
}

type State struct {
    priority, cost, pos int
    s string
}

type PriorityQueue []State

func (p PriorityQueue) Len() int {
    return len(p)
}

func (p PriorityQueue) Less(i, j int) bool {
    if p[i].priority != p[j].priority {
        return  p[i].priority < p[j].priority
    } else {
        if p[i].cost != p[j].cost {
            return p[i].cost < p[j].cost
        } else {
            return p[i].pos < p[j].pos
        }
    }
}

func (p PriorityQueue) Swap(i, j int) {
    p[i], p[j] = p[j], p[i]
}

func (p *PriorityQueue) Push(v interface{}) {
    *p = append(*p, v.(State))
}

func (p *PriorityQueue) Pop() (v interface{}) {
    v, *p = (*p)[p.Len()-1], (*p)[:p.Len()-1]
    return v
}

官方C++

class Solution {
public:
    int minSwap(const string &s1, const string &s2, const int &pos) {
        int tot = 0;
        for (int i = pos; i < s1.size(); i++) {
            tot += s1[i] != s2[i];
        }
        return (tot + 1) / 2;
    }

    int kSimilarity(string s1, string s2) {
        typedef tuple<int, int, int, string> State;
        int n = s1.size();
        priority_queue<State, vector<State>, greater<State>> pq;
        unordered_set<string> visit;
        pq.emplace(0, 0, 0, s1);
        visit.emplace(s1);
        while (!pq.empty()) {
            auto [_, cost, pos, cur] = pq.top();
            pq.pop();
            if (cur == s2) {
                return cost;
            }
            while (pos < n && cur[pos] == s2[pos]) {
                pos++;
            }
            for (int j = pos + 1; j < n; j++) {
                if (s2[j] == cur[j]) {
                    continue;
                }
                if (s2[pos] == cur[j]) {
                    swap(cur[pos], cur[j]);
                    if (!visit.count(cur)) {
                        visit.emplace(cur);
                        pq.emplace(cost + 1 + minSwap(s2, cur, pos + 1), cost + 1, pos + 1, cur);
                    }
                    swap(cur[pos], cur[j]);
                }
            }
        } 
        return 0;
    }
};

方法四:动态规划

参考官方题解:https://leetcode.cn/problems/k-similar-strings/solution/xiang-si-du-wei-k-de-zi-fu-chuan-by-leet-8z10/

LeetCode题库地址