題目描述
這是 LeetCode 上的 854. 相似度為 K 的字元串 ,難度為 困難。
Tag : 「啟發式搜尋」、「AStar 算法」
對于某些非負整數
k
,如果交換 中兩個字母的位置恰好
k
次,能夠使結果字元串等于 ,則認為字元串 和 的 相似度為
k
。
給你兩個字母異位詞 和 ,傳回 和 的相似度
k
的最小值。
示例 1:
輸入:s1 = "ab", s2 = "ba"
示例 2:
輸入:s1 = "abc", s2 = "bca"
提示:
-
和s1
隻包含集合s2
中的小寫字母{'a', 'b', 'c', 'd', 'e', 'f'}
-
是s2
的一個字母異位詞s1
AStar 算法
問題本質為将
s1
轉換為
s2
的最小操作次數,由于題目確定了
s1
和
s2
互為字母異位詞(必然有解),是以最好的求解方式是使用 AStar 算法。
可直接根據本題規則來設計 AStar 的啟發式函數: 對于兩個狀态
a
和
b
直接計算出「理論最小轉換次數」: 不同字元串的轉換成本之和,由于每一次交換最多可減少兩個不同的字元,我們可計算
a
與
b
的不同字元數量 ,對應的理論最小轉換次數為 。
需要注意的是:由于我們衡量某個字元
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 {
char c =
- 時間複雜度:啟發式搜尋不分析時空複雜度
- 空間複雜度:啟發式搜尋不分析時空複雜度
最後
這是我們「刷穿 LeetCode」系列文章的第
No.854
篇,系列開始于 2021/01/01,截止于起始日 LeetCode 上共有 1916 道題目,部分是有鎖題,我們将先把所有不帶鎖的題目刷完。
在這個系列文章裡面,除了講解解題思路以外,還會盡可能給出最為簡潔的代碼。如果涉及通解還會相應的代碼模闆。