给定两个字符串, 判断最少经过多少次swap 可以使得 两个字符串一致,
首先类似的问题 是存在一个 underlying graph 的。
每一个字母都对应着一个节点,两个字符串的不一致表示为图上的一条有向边
最后的问题转化为 图上的最(多)圆环分解
要注意的是贪心策略并不能解决问题(即每一次都选择 最小的那个圆环构成原图)
1
使用记忆化搜索策略。
将一个所有可能存在路径存在与否的向量作为相应的状态
超时的代码
class Solution:
def kSimilarity(self, A, B):
"""
:type A: str
:type B: str
:rtype: int
"""
if (A==B):
return 0
N = len(A)
alp ='abcdef'
pairs = [(a,b) for a in alp for b in alp if a!=b]
# 所有可能的路径
index = {p:i for i,p in enumerate(pairs)}
# 所有可能的路径的编号
count = [0]*len(index)
#可能路径的数目(原图) 也就是初始状态
for a,b in zip(A,B):
if(a!=b):
count[index[(a,b)]]= count[index[a,b]]+ 1
seen = set()
#所有可能出现的环
for size in range(2,len(alp) + 1):
for cand in itertools.permutations(alp,size):
# 最小的哪一个出发节点为是顺序(防止重复)
i = cand.index(min(cand))
seen.add (cand[i:]+cand[:i])
possibles = [] #所有可能的环的路径表示 状态之间的 路径
for cand in seen:
row = [0] * len(alp)*(len(alp)-1)
for a,b in zip(cand,cand[1:]+cand[:1]):
row[index[a,b]]= row[index[a,b]]+1
possibles.append(row)
ZERO = tuple([0]*len(row))
memo = {ZERO:0}
# print(possibles)
def solve(count):
if count in memo :
return memo[count]
ans =float('-inf')
#所有可能的路径
for row in possibles :
count2 = list(count)
for i,x in enumerate(row):
if count2[i]>=x :
count2[i] = count2[i] - x
else:
break
else:
ans = max(ans,1 + solve(tuple(count2)))
memo[count]= ans
return ans
return sum(count)-solve(tuple(count))
2 暴力进行广度优先搜索
因为可以证明最优解可以是永远更换第一个不匹配的字母到合适的位置,那么我可以将一个节点的边数目由 N2 下降到N
而并不是所有意义上的交换 这种复杂度降低是非常有必要的
结论: 这种数据量比较小的问题往往比较复杂,尤其可能没有多项式解
class Solution:
def kSimilarity(self, A, B):
"""
:type A: str
:type B: str
:rtype: int
"""
def nei(S):
for i,c in enumerate(S):
if c!= B[i]:
break
T=list(S)
for j in range(i+1,len(S)):
if(S[j]==B[i]):
T[i],T[j]= T[j],T[i]
yield "".join(T)
T[j],T[i]=T[i],T[j]
queue= collections.deque([A])
seen ={A:0}
while(queue):
S=queue.popleft()
if(S==B):
return seen[S]
for T in nei(S):
if T not in seen:
seen[T]=seen[S]+1
queue.append(T)
这里有一个 fiield 迭代器的用发 可以学习一个