鉴于A
B
L
个L
的两个排列n
,m
是偶数,我们称这些排列为对称"对称" (缺少一个更好的术语),如果存在m > n
和 - A[n:m] == B[L-m:L-n]
- B[n:m] == A[L-m:L-n]
- all other elements are in place
,A = 0 1 2 3 4 5 6 7
,如(用python表示法):
1 2
非正式地,请考虑
5 6
取任意一片,例如B = 0 5 6 3 4 1 2 7
。它从第二个索引开始,长度为2.现在采用对称的切片:它以倒数第二个索引结束,也是2个字符长,所以它是A
。交换这些切片给出了
B
现在,n=1, m=3
和A = 0 1 2 3 4 5 6 7
B = 1 0 2 3 4 5 7 6
是"对称"在上述意义上(n,m
)。另一方面
n
不是"对称" (不存在具有上述属性的m
)。
如何在python中编写一个算法,找出两个给定的排列(=列表)是否是#34;对称的"如果是,请找到L
和set(A)==set(B), len(set(A))==len(A)
?为简单起见,我们只考虑偶数L
(因为奇数情况可以通过消除中间固定元素而简单地减少到偶数)并假设正确的输入(testing 0123456789 L= 10
test_alexis ok in 15.4252s
test_evgeny_kluev_A ok in 30.3875s
test_evgeny_kluev_B ok in 27.1382s
test_evgeny_kluev_C ok in 14.8131s
test_ian ok in 26.8318s
test_jared_goguen ok in 10.0999s
test_jason_herbburn ok in 21.3870s
test_tom_karzes ok in 27.9769s
)。
(我毫无疑问地提出了所有可能的对称性,但寻找更智能,更快速的东西)。
有趣的事实:给定div
的对称排列数为Triangular number。
赏金更新:这里有很多优秀的答案。 @Jared Goguen's solution似乎是最快的。
最后时间:
<div style="width:50px; height:100px" id="box" div>
答案 0 :(得分:5)
以下是该问题的可行解决方案:
def isSymmetric(A, B):
L = len(A) #assume equivalent to len(B), modifying this would be as simple as checking if len(A) != len(B), return []
la = L//2 # half-list length
Al = A[:la]
Ar = A[la:]
Bl = B[:la]
Br = B[la:]
for i in range(la):
lai = la - i #just to reduce the number of computation we need to perform
for j in range(1, lai + 1):
k = lai - j #same here, reduce computation
if Al[i] != Br[k] or Ar[k] != Bl[i]: #the key for efficient computation is here: do not proceed unnecessarily
continue
n = i #written only for the sake of clarity. i is n, and we can use i directly
m = i + j
if A[n:m] == B[L-m:L-n] and B[n:m] == A[L-m:L-n]: #possibly symmetric
if A[0:n] == B[0:n] and A[m:L-m] == B[m:L-m] and A[L-n:] == B[L-n:]:
return [n, m]
return []
正如你所提到的,虽然的想法看起来很简单,但它实际上是一个非常棘手的。然而,一旦我们看到模式,实现就是直截了当的。
解决方案的核心思想是这一行:
if Al[i] != Br[k] or Ar[k] != Bl[i]: #the key for efficient computation is here: do not proceed unnecessarily
所有其他行只是问题陈述中的直接代码转换,或者是为了更有效的计算而进行的优化。
找到解决方案涉及的步骤很少:
首先,我们需要将每个列表A
和列表B
拆分为两个半列表(称为Al
,Ar
,Bl
和Br
)。每个半列表将包含原始列表的一半成员:
Al = A[:la]
Ar = A[la:]
Bl = B[:la]
Br = B[la:]
其次,为了使评估高效,此处的目标是找到我称之为枢轴索引来决定是否列表中的位置(索引)值得评估或不检查列表是否对称。此数据透视索引是查找高效解决方案的核心理念。所以我会尝试详细说明一下:
考虑A
列表的左半部分,假设您有这样的成员:
Al = [al1, al2, al3, al4, al5, al6]
我们可以想象对于上面提到的列表有一个相应的索引列表
Al = [al1, al2, al3, al4, al5, al6]
iAl = [0, 1, 2, 3, 4, 5 ] #corresponding index list, added for explanation purpose
(注意:我提到想象一个相应的索引列表的原因是为了便于解释)
同样,我们可以想象其他三个列表可能有类似的索引列表。我们将它们分别命名为iAr
,iBl
和iBr
,并且它们都具有iAl
的相同成员。
列表的索引对我们来说真正重要 - 为了解决问题。
这就是我的意思:假设我们有两个参数:
i
,我会使用符号^
作为当前i
)j
,我会用符号==
直观地表示它的长度值。对于iAl
中索引元素的每个评估 - 然后每个评估意味着:
在
i
中给出索引值j
和长度值iAl
, 确定是否值得检查对称的东西 从索引开始并具有长度的资格 (因此名称支点索引来)。
现在,让我们以i = 0
和j = 1
为例,举一个评估。评估结果如下:
iAl = [0, 1, 2, 3, 4, 5]
^ <-- now evaluate this index (i) = 0
== <-- now this has length (j) of 1
为了进一步评估那些索引 i
和长度 j
,那么对应的iBr
必须具有与相同 长度的相同的项值,但在不同的索引上(让我们将其命名为 index k
)
iBr = [0, 1, 2, 3, 4, 5]
^ <-- must compare the value in this index to what is pointed by iAl
== <-- must evaluate with the same length = 1
例如,对于上述情况,这是可能的&#34;对称&#34;仅针对两个列表Al-Br
的排列(稍后我们将考虑另外两个列表Ar-Bl
):
Al = [0, x, x, x, x, x] #x means don't care for now
Br = [x, x, x, x, x, 0]
此时,最好注意
即使上述条件不是,也不值得进一步评估 真
这就是让算法更加高效的地方;也就是说,通过有选择地仅评估所有可能情况中的少数可能的情况。如何找到几个可能的案例?
试图找到索引和长度之间的关系 四个清单。也就是说,对于给定的索引
i
和长度j
列表(例如Al
),对应中的索引k
必须是什么 列表(在这种情况下是Br
)。对应列表的长度不需要 找到它,因为它与列表中的相同(即j
)。
知道了,现在让我们继续看看我们是否可以在评估过程中看到更多模式。
现在考虑长度(j
)的影响。例如,如果我们要从索引 0
进行评估,但长度为2
,则对应列表需要具有不同的索引 { {1}}评估的时间长度为k
1
或者,如上图所示,真正重要的是狐狸iAl = [0, 1, 2, 3, 4, 5]
^ <-- now evaluate this index (i) = 0
===== <-- now this has length (j) of 2
iBr = [0, 1, 2, 3, 4, 5]
^ <-- must compare the value in this index to what is pointed by iAl
===== <-- must evaluate with the same length = 2
和i = 0
是这样的:
y = 2
看一看上面的模式与# when i = 0 and y = 2
Al = [0, y, x, x, x, x] #x means don't care for now
Br = [x, x, x, x, 0, y] #y means to be checked later
和i = 0
时的情况略有不同 - 示例中y = 1
值的索引位置已移位:
0
因此,长度会移动,其中必须检查对应列表的索引。在第一种情况下,当# when i = 0 and y = 1, k = 5
Al = [0, x, x, x, x, x] #x means don't care for now
Br = [x, x, x, x, x, 0]
# when i = 0 and y = 2, k = 4
Al = [0, y, x, x, x, x] #x means don't care for now
Br = [x, x, x, x, 0, y] #y means to be checked later
和i = 0
时,y = 1
。但在第二种情况下,当k = 5
和i = 0
时,y = 1
。因此,当我们为固定的索引 k = 4
更改长度 j
时,我们找到了支点索引关系案例为i
)到对应列表索引 0
。
现在,考虑具有固定长度 k
的索引 i
对对应列表索引 {{的影响1}}。例如,我们将长度修复为j
,然后对于索引 k
,我们有:
y = 4
在上面的示例中,可以看出我们需要为给定的i = 0
和iAl = [0, 1, 2, 3, 4, 5]
^ <-- now evaluate this index (i) = 0
========== <-- now this has length (j) of 4
iAl = [0, 1, 2, 3, 4, 5]
^ <-- now evaluate this index (i) = 1
========== <-- now this has length (j) of 4
iAl = [0, 1, 2, 3, 4, 5]
^ <-- now evaluate this index (i) = 2
========== <-- now this has length (j) of 4
#And no more needed
评估 3 的可能性,但是如果索引 i
更改为j
,长度为i
:
1
请注意,我们只需要评估 2 的可能性。因此,索引 j = 4
的增加会减少要评估的可能案例的数量!
通过上述所有模式,我们几乎找到了使算法运行所需的所有基础。但要完成此操作,我们需要找到iAl = [0, 1, 2, 3, 4, 5]
^ <-- now evaluate this index (i) = 1
========== <-- now this has length (j) of 4
iAl = [0, 1, 2, 3, 4, 5]
^ <-- now evaluate this index (i) = 2
========== <-- now this has length (j) of 4
对{strong} {<1}}中显示的索引与{{中的索引之间的关系1}}对同一i
。
现在,我们实际上可以看到它们只是镜像我们在Al-Br
对中找到的关系!
(恕我直言,这真的很美!所以我认为术语&#34;对称&#34;排列与真相并不远)
例如,如果我们使用[i, j] => [k, j]
和Ar-Bl
[i, j]
对
Al-Br
然后,要使其对称,我们必须拥有相应的Al-Br
:
i = 0
y = 2
对的索引 镜像(或对称)Al = [0, y, x, x, x, x] #x means don't care for now
Br = [x, x, x, x, 0, y] #y means to be checked later
对的索引!
因此,结合我们上面找到的所有模式,我们现在可以找到数据透视索引来评估Ar-Bl
,Ar = [x, x, x, x, 3, y] #x means don't care for now
Bl = [3, y, x, x, x, x] #y means to be checked later
,Al-Br
和{{ 1}}。
我们只需要检查枢轴索引中列表的值 第一。如果
Ar-Bl
,Al
,Ar
和Bl
的数据透视索引中的列表值 我们需要检查评估 中的匹配然后才 对于对称标准(从而使计算有效!)
将上述所有知识放入代码中,以下是检查对称性的Br
Python代码:
Al
然后你会进行对称测试!
(好吧,这是一个相当大的挑战,我需要花一点时间来弄清楚如何。)
答案 1 :(得分:4)
我重写了代码而没有一些复杂性(和错误)。
def test_o_o(a, b):
L = len(a)
H = L//2
n, m = 0, H-1
# find the first difference in the left-side
while n < H:
if a[n] != b[n]: break
n += 1
else: return
# find the last difference in the left-side
while m > -1:
if a[m] != b[m]: break
m -= 1
else: return
# for slicing, we want end_index+1
m += 1
# compare each slice for equality
# order: beginning, block 1, block 2, middle, end
if (a[0:n] == b[0:n] and \
a[n:m] == b[L-m:L-n] and \
b[n:m] == a[L-m:L-n] and \
a[m:L-m] == b[m:L-m] and \
a[L-n:L] == b[L-n:L]):
return n, m
实施既优雅又高效。
break
到else: return
结构确保函数尽可能快地返回。他们还验证n
和m
已设置为有效值,但在显式检查切片时似乎不需要这样做。可以移除这些线,而不会对时间产生明显影响。
一旦评估为False
,显式比较切片也会短路。
最初,我通过将b
转换为a
来检查排列是否存在:
b = b[:]
b[n:m], b[L-m:L-n] = b[L-m:L-n], b[n:m]
if a == b:
return n, m
但这比明确比较切片要慢。让我知道这个算法是否能说不出话来,我可以提供进一步的解释(甚至可以证明)它为何起作用并且是最小的。
答案 2 :(得分:4)
我尝试为此任务实现3种不同的算法。它们都具有O(N)时间复杂度并且需要O(1)额外空间。有趣的事实:所有其他答案(到目前为止已知)实现了这些算法中的2个(尽管它们并不总是保持最佳的渐近时间/空间复杂度)。以下是每种算法的高级描述:
算法A
算法B
算法C
每种算法的步骤1有两种替代实现:(1)使用itertools,(2)使用普通循环(或列表推导)。 itertools对于长列表有效,但在短列表上相对较慢。
这是算法C,第一步使用itertools实现。它看起来比其他两种算法更简单(在本文末尾)。它很快,即使是短名单:
import itertools as it
import operator as op
def test_C(a, b):
length = len(a)
half = length // 2
mismatches = it.imap(op.ne, a, b[:half]) # compare half-lists
try:
n = next(it.compress(it.count(), mismatches))
nr = length - n
mr = a.index(b[n], half, nr)
m = length - mr
except StopIteration: return None
except ValueError: return None
if a[n:m] == b[mr:nr] and b[n:m] == a[mr:nr] \
and a[m:mr] == b[m:mr] and a[nr:] == b[nr:]:
return (n, m)
这可以使用大多数itertools来完成:
def test_A(a, b):
equals = it.imap(op.eq, a, b) # compare lists
e1, e2 = it.tee(equals)
l = it.chain(e1, [True])
r = it.chain([True], e2)
borders = it.imap(op.ne, l, r) # delimit equal/non-equal intervals
ranges = list(it.islice(it.compress(it.count(), borders), 5))
if len(ranges) == 4:
n1, m1 = ranges[0], ranges[1]
n2, m2 = ranges[2], ranges[3]
elif len(ranges) == 2:
n1, m1 = ranges[0], len(a) // 2
n2, m2 = len(a) // 2, ranges[1]
else:
return None
if n1 == len(a) - m2 and m1 == len(a) - n2 \
and a[n1:m1] == b[n2:m2] and b[n1:m1] == a[n2:m2]:
return (n1, m1)
@j_random_hacker在OP注释中已经提供了该算法的高级描述。以下是一些细节:
从比较列表开始:
A 0 1 2 3 4 5 6 7
B 0 5 6 3 4 1 2 7
= E N N E E N N E
然后找到相等/不等间隔之间的边界:
= E N N E E N N E
B _ * _ * _ * _ *
然后确定不相等元素的范围:
B _ * _ * _ * _ *
[1 : 3] [5 : 7]
然后检查是否确实有2个范围(当两个范围在中间相遇时有特殊情况),范围本身是对称的,它们的内容也是。
其他替代方法是使用itertools处理每个列表的一半。这允许稍微简单(并且稍微更快)的算法,因为不需要处理特殊情况:
def test_B(a, b):
equals = it.imap(op.eq, a, b[:len(a) // 2]) # compare half-lists
e1, e2 = it.tee(equals)
l = it.chain(e1, [True])
r = it.chain([True], e2)
borders = it.imap(op.ne, l, r) # delimit equal/non-equal intervals
ranges = list(it.islice(it.compress(it.count(), borders), 2))
if len(ranges) != 2:
return None
n, m = ranges[0], ranges[1]
nr, mr = len(a) - n, len(a) - m
if a[n:m] == b[mr:nr] and b[n:m] == a[mr:nr] \
and a[m:mr] == b[m:mr] and a[nr:] == b[nr:]:
return (n, m)
答案 3 :(得分:3)
我构建了列表B中字符所在的地图,然后使用它来确定列表A中隐含的子范围。一旦我有了子范围,我就可以理智地检查一些信息,并比较切片。
如果A[i] == x
,则x
出现在B中的哪个位置?拨打该职位p
。
我知道i
,左侧子范围的开始。
我知道L (= len(A))
,所以我知道L-i
,右侧子范围的结束。
如果我知道p
,那么我知道右子范围的暗示开始,假设B [p]和A [i]是对称范围对的开始。因此,如果列表是对称的,OP的L - m
将为p
。
设置L-m == p
会给我m
,所以我有四个终点。
完整性测试是:
如果所有这些都结账,请比较A [左] == B [右]和B [左] == A [右]。如果为真,请返回left
。
def find_symmetry(a:list, b:list) -> slice or None:
assert len(a) == len(b)
assert set(a) == set(b)
assert len(set(a)) == len(a)
length = len(a)
assert length % 2 == 0
half = length // 2
b_loc = {bi:n for n,bi in enumerate(b)}
for n,ai in enumerate(a[:half]):
L_n = length - 1 - n # L - n
L_m = b_loc[ai] # L - m (speculative)
if L_m < half: # Sanity: bail if on wrong side
continue
m = b_loc[a[L_n]] # If A[n] starts range, A[m] ends it.
if m < n or m > half: # Sanity: bail if backwards or wrong side
continue
left = slice(n, m+1)
right = slice(L_m, L_n+1)
if a[left] == b[right] and \
b[left] == a[right]:
return left
return None
res = find_symmetry(
[ 10, 11, 12, 13, 14, 15, 16, 17, ],
[ 10, 15, 16, 13, 14, 11, 12, 17, ])
assert res == slice(1,3)
res = find_symmetry(
[ 0, 1, 2, 3, 4, 5, 6, 7, ],
[ 1, 0, 2, 3, 4, 5, 7, 6, ])
assert res is None
res = find_symmetry("abcdefghijklmn", "nbcdefghijklma")
assert res == slice(0,1)
res = find_symmetry("abcdefghijklmn", "abjklfghicdmen")
assert res == slice(3,4)
res = find_symmetry("abcdefghijklmn", "ancjkfghidelmb")
assert res == slice(3,5)
res = find_symmetry("abcdefghijklmn", "bcdefgaijklmnh")
assert res is None
res = find_symmetry("012345", "013245")
assert res == slice(2,3)
答案 4 :(得分:3)
这是正确的事情:
Br = B[L//2:]+B[:L//2]
same_full = [a==b for (a,b) in zip(A, Br)]
same_part = [a+b for (a,b) in zip(same_full[L//2:], same_full[:L//2])]
for n, vn in enumerate(same_part):
if vn != 2:
continue
m = n
for vm in same_part[n+1:]:
if vm != 2:
break
m+=1
if m>n:
print("n=", n, "m=", m+1)
我很确定你可以做一些更好的计数,但是...... meh
答案 5 :(得分:3)
我相信以下伪代码应该有效:
i
的第一个元素A[i] != B[i]
,设置n = i
。如果没有这样的元素,则返回success
。如果n >= L/2
,请返回fail
。i > n
的第一个元素A[i] == B[i]
,设置m = i
。如果没有此类元素或m > L/2
,请设置m = L/2
。A[0:n] == B[0:n]
,A[n:m] == B[L-m:L-n]
,B[n:m] == A[L-m:L-n]
,A[m:L-m] == B[m:L-m]
和A[L-n:L] == B[L-n:L]
。如果一切正常,请返回success
。否则,请返回fail
。复杂性是O(n),它应该是最低的,因为总是需要比较列表中的所有元素。
答案 6 :(得分:3)
这是一个简单的解决方案,通过我和我的测试:
A
。结果是否与B
匹配?算法为O(N);没有嵌入式循环,显式或隐式。
在步骤1中,我需要检测交换的子串是否相邻的情况。这只能发生在字符串的中间,但我发现更容易只注意移动的部分的第一个元素(firstval
)。与显式检查所有约束相比,第2步更简单(因此更不容易出错)。
def compare(A, B):
same = True
for i, (a, b) in enumerate(zip(A,B)):
if same and a != b: # Found the start of a presumed transposition
same = False
n = i
firstval = a # First element of the transposed piece
elif (not same) and (a == b or b == firstval): # end of the transposition
m = i
break
# Construct the transposed string, compare it to B
origin = A[n:m]
if n == 0: # swap begins at the edge
dest = A[-m:]
B_expect = dest + A[m:-m] + origin
else:
dest = A[-m:-n]
B_expect = A[:n] + dest + A[m:-m] + origin + A[-n:]
return bool(B_expect == B)
样品使用:
>>> compare("01234567", "45670123")
True
加分:我相信这段关系的名称将是“对称块换位”。 块换位交换两个子序列,取{ {1}}到ABCDE
。 (见定义4 here;我实际上通过谷歌搜索“ADCBE”找到了这个。我在名称中添加了“对称”来描述长度条件。
答案 7 :(得分:3)
制作索引列表(ds),其中两个列表的前半部分不同。 可能的n是第一个这样的索引,最后一个这样的索引是m - 1。 检查是否有效对称。 len(ds)== m - n确保没有任何差距。
2016-02-26-03-43/
答案 8 :(得分:3)
这是一个传递测试代码的O(N)解决方案:
def sym_check(a, b):
cnt = len(a)
ml = [a[i] == b[i] for i in range(cnt)]
sl = [i for i in range(cnt) if (i == 0 or ml[i-1]) and not ml[i]]
el = [i+1 for i in range(cnt) if not ml[i] and (i == cnt-1 or ml[i+1])]
assert(len(sl) == len(el))
range_cnt = len(sl)
if range_cnt == 1:
start1 = sl[0]
end2 = el[0]
if (end2 - start1) % 2 != 0:
return None
end1 = (start1 + end2) // 2
start2 = end1
elif range_cnt == 2:
start1, start2 = sl
end1, end2 = el
else:
return None
if end1 - start1 != end2 - start2:
return None
if start1 != cnt - end2:
return None
if a[start1:end1] != b[start2:end2]:
return None
if b[start1:end1] != a[start2:end2]:
return None
return start1, end1
我只用Python 2测试过它,但我相信它也适用于Python 3。
它标识两个列表不同的范围。它寻找两个这样的范围(如果只有一个这样的范围,它会尝试将其除以一半)。然后检查两个范围是否相同,并且相对于彼此处于适当的位置。如果是,则检查范围中的元素是否匹配。
答案 9 :(得分:3)
又一个版本:
def compare(a, b):
i_zip = list(enumerate(zip(a, b)))
llen = len(a)
hp = llen // 2
def find_index(i_zip):
for i, (x, y) in i_zip:
if x != y:
return i
return i_zip[0][0]
# n and m are determined by the unmoved items:
n = find_index(i_zip[:hp])
p = find_index(i_zip[hp:])
m = llen - p
q = llen - n
# Symmetric?
if a[:n] + a[p:q] + a[m:p] + a[n:m] + a[q:] != b:
return None
return n, m
此解决方案基于:
所有符合对称性要求的有效置换列表对A,B将具有以下结构:
A = P1 + P2 + P3 + P4 + P5
B = P1 + P4 + P3 + P2 + P5
^n ^m ^hp ^p ^q <- indexes
,len(P1)== len(P5)和len(P2)== len(P4)
n
,m
,则上述函数的最后3行将确定正确的解。 (p
&amp; q
只是m
&amp; n
的镜像索引。n
是确定A和B的项目何时开始分歧的问题。接下来,相同的方法应用于从中点p
开始查找hp
。 m
只是p
的镜像索引。找到所有涉及的索引并出现解决方案。