如果可以通过将 A 中的两个小写字母精确地交换位置 K 次得到与 B 相等的字符串,我们称字符串 A 和 B 的相似度为 K(K 为非负整数)。
给定两个字母异位词 A 和 B ,返回 A 和 B 的相似度 K 的最小值。
示例 1:
输入:A = "ab", B = "ba"
输出:1
示例 2:
输入:A = "abc", B = "bca"
输出:2
示例 3:
输入:A = "abac", B = "baca"
输出:2
示例 4:
输入:A = "aabc", B = "abca"
输出:2
提示:
1 <= A.length == B.length <= 20
A 和 B 只包含集合 {'a', 'b', 'c', 'd', 'e', 'f'} 中的小写字母。
备注:本题我采用的dfs+剪枝通过的,姿势比较丑,于是学习了官方题解的两种做法,受益匪浅。
思路:一般这类题思考的方向基本上要往置换群上想,也就是找到若干个环,使得分别在这些环内进行交换使得最终两字符串相等,因为我们知道在一个环内使得置换后使得每个字母在相应位置上的移动次数为环内点的个数减一,这使得我们对于本题采用的贪心思想就是在满足条件的情况下,使得环的数量尽可能的多。
我们对两个字符串构造一个包含6个节点(a,b,c,d,e,f)的基础图,对应第i位连一条a[i]->b[i]的有向边,并且该图允许重边和自环,若两字符串相等,则该图只存在自环,我们的最终目标就是基于前述讲的贪心思想把所有的边变成自环。
方法一:动态规划
我们用p1,p2...表示所有可能的环,对于一个环pi,我们可以用一个01数组表示每条边是否出现过。我们设numCycle(G)表示基础图G最多的拆分数目,通过枚举环C,检查C是否包含于G。
class Solution {
private Map<String,Integer> map;
private String[] alphabet= {"a","b","c","d","e","f"};
public int kSimilarity(String A, String B) {
if(A.equals(B)) return 0;
int ans=0;
int n=A.length();
map=new HashMap<>();
int[] count=new int[6*6];
for(int i=0;i<n;i++) {
if(A.charAt(i)!=B.charAt(i)) {
count[6*(A.charAt(i)-'a')+(B.charAt(i)-'a')]++;
ans++;
}
}
List<int[]> possibles=new ArrayList<>();
for(int size=2;size<=6;size++) {
search: for(String cycle : permutations(alphabet,0,size)) {
for(int i=1;i<size;i++)
if(cycle.charAt(i)<cycle.charAt(0))
continue search;
int[] row=new int[count.length];
for(int i=0;i<size;i++) {
int u=cycle.charAt(i)-'a';
int v=cycle.charAt((i+1)%size)-'a';
row[6*u+v]++;
}
possibles.add(row);
}
}
int[] zero=new int[count.length];
//System.out.println(Arrays.toString(zero));
map.put(Arrays.toString(zero), 0);
return ans-numCycles(possibles,count);
}
private int numCycles(List<int[]> possibles,int[] count) {
String countS=Arrays.toString(count);
if(map.containsKey(countS))
return map.get(countS);
int ans=Integer.MIN_VALUE;
search:for(int[] row : possibles) {
int[] count2=count.clone();
for(int i=0;i<row.length;i++) {
if(count2[i]>=row[i])
count2[i]-=row[i];
else
continue search;
}
ans=Math.max(ans, 1+numCycles(possibles,count2));
}
map.put(countS, ans);
return ans;
}
private List<String> permutations(String[] alphabet,int used,int size){
List<String> ans=new ArrayList<>();
if(size==0) {
ans.add(new String(""));
return ans;
}
for(int b=0;b<alphabet.length;b++) {
if(((used>>b)&1)==0) {
for(String next : permutations(alphabet,used|(1<<b),size-1))
ans.add(alphabet[b]+next);
}
}
return ans;
}
}
方法二:广度优先搜索
我们每次从左到右找到第一个a[i]!=b[i]对应的呢条边,即在字符串a和b中,每次找到最左侧满足a[i]!=b[i]的i,并找到所有j>i且a[j]=b[i]的j,使得i位置形成自环,剩下的状态继续遍历,遍历的方式我们可以采用广度优先搜索。
class Solution {
public int kSimilarity(String A, String B) {
Queue<String> q=new ArrayDeque<>();
q.add(A);
Map<String,Integer> dist=new HashMap<>();
dist.put(A, 0);
while(!q.isEmpty()) {
String now=q.poll();
if(now.equals(B))
return dist.get(now);
for(String next : neighbor(now,B)) {
if(!dist.containsKey(next)) {
dist.put(next, dist.get(now)+1);
q.add(next);
}
}
}
return -1;
}
private List<String> neighbor(String S,String target){
List<String> ans=new ArrayList<>();
int i=0;
for(;i<S.length();i++)
if(S.charAt(i)!=target.charAt(i))
break;
char[] T=S.toCharArray();
for(int j=i+1;j<S.length();j++)
if(S.charAt(j)==target.charAt(i)) {
swap(T,i,j);
ans.add(new String(T));
swap(T,i,j);
}
return ans;
}
private void swap(char[] T,int i,int j) {
char tmp=T[i];
T[i]=T[j];
T[j]=tmp;
}
}