在字符串上考虑以下函数:
int F(string S)
{
int N = S.size();
int T = 0;
for (int i = 0; i < N; i++)
for (int j = i + 1; j < N; j++)
if (S[i] > S[j])
T++;
return T;
}
长度为N的字符串S0,所有成对不同的字符总共有N!独特的排列。
例如“bac”具有以下6种排列:
bac
abc
cba
bca
acb
cab
考虑这些N!按字典顺序排列的字符串:
abc
acb
bac
bca
cab
cba
现在考虑将F应用于每个字符串:
F("abc") = 0
F("acb") = 1
F("bac") = 1
F("bca") = 2
F("cab") = 2
F("cba") = 3
给定这组排列的一些字符串S1,我们想要找到集合中的下一个字符串S2,它与S1具有以下关系:
F(S2) == F(S1) + 1
例如,如果S1 ==“acb”(F = 1)而不是S2 ==“bca”(F = 1 + 1 = 2)
这样做的一种方法是从S1开始,并遍历排列列表,寻找F(S)= F(S1)+1。不幸的是O(N!)。
通过S1上的O(N)函数可以直接计算S2?
答案 0 :(得分:0)
假设S1的长度为n,F(S1)
的最大值为n(n-1)/2
,如果F(S1) = n(n-1)/2
,则表示它是最后一个函数,并且没有任何下一个函数,但是如果{ {1}},表示至少有一个字符F(S1) < n(n-1)/2
大于字符x
而y
位于x
旁边,找到这样的y
索引最低,并更改x和y位置。让我们看看它的例子:
S1 ==“acb”(F = 1),1&lt; 3所以有一个char x
比另一个char x
大,而且它的索引大于y
,这里最小的索引y
是x
,并且首先尝试将其替换为c
(小于a
,因此算法在这里完成)==&gt; S2 =“cab”,F(S2)= 2。
现在让我们用S2测试它,cab:x = b,y = a,==&gt; S3 =“cba”。\
查找x
并不难,迭代输入,并将变量名称x
,而当前访问的字符小于min
,将min
设置为新的访问过char,并访问下一个字符,第一次访问大于min
停止迭代的字符时,这是min
:
这是c#中的伪代码(但是我对input.Substring中的边界并不小心):
x
答案 1 :(得分:0)
以下是解决问题的算法大纲。
我假设你有一个函数可以直接返回n
- 排列(给定n
)及其反函数,即给定置换的函数返回n
。这些分别为perm(n)
和perm'(n)
。
如果我认为它正确,当你有一个4个字母的字符串来置换函数F就像这样:
F("abcd") = 0
F("abdc") = 1
F(perm(3)) = 1
F(...) = 2
F(...) = 2
F(...) = 3
F(perm(7)) = 1
F(...) = 2
F(...) = 2
F(...) = 3
F(...) = 3
F(...) = 4
F(perm(13)) = 2
F(...) = 3
F(...) = 3
F(...) = 4
F(...) = 4
F(...) = 5
F(perm(19)) = 3
F(...) = 4
F(...) = 4
F(...) = 5
F(...) = 5
F(perm(24)) = 6
用文字表示,当你从3个字母变为4个时,你得到4个F表值的副本,分别在(第1个,第2个,第3个,第4个)副本中加入(0,1,2,3)。例如,在第二种情况下,通过将第二个字母放在第一位,你已经有一个紊乱;这只是添加到与原始3个字母字符串相同的模式中的其他紊乱。
从这个大纲来看,编写函数F应该不会太困难(但我现在还没有时间)。严格来说F的倒数不是函数,因为它是多值的,但是给定n
和F(n)
,只有少数情况可以找到m
st F(m)==F(n)+1
。这些案例是:
n == N!
其中N
是字符串中的字母数,没有下一个排列; F(n+1) < F(n)
,所寻求的解决方案是perm(n+(N-1)!)
,F(n+1) == F(n)
,解决方案为perm(n+2)
; F(n+1) > F(n)
,解决方案为perm(n+1)
。我怀疑其中一些可能仅适用于4个字母的字符串,其中一些条款必须根据K字母排列进行调整。
答案 2 :(得分:0)
这不是O(n)
,但它至少是O(n²)
(其中n是排列中元素的数量,在您的示例3中)。
首先,请注意,无论何时在字符串中放置一个字符,您都已经知道F的增加有多大意义 - 然而,它还有许多字符小于那个尚未添加到字符串中的字符
这为我们提供了另一种计算F(n)的算法:
used = set()
def get_inversions(S1):
inv = 0
for index, ch in enumerate(S1):
character = ord(ch)-ord('a')
cnt = sum(1 for x in range(character) if x not in used)
inv += cnt
used.add(character)
return inv
这并不比原版本好多了,但是在反转F时很有用。你想知道第一个字典缩小字符串的字符串 - 因此,复制原始字符串是有意义的,只有在必要时才更改它。当需要进行此类更改时,我们还应尽可能少地更改字符串。
为此,让我们使用F的最大值为n
个字母的字符串为n(n-1)/2
的信息。如果我们没有更改原始字符串,每当所需的反转次数大于此数量时,这意味着我们必须在该点交换一个字母。 Python中的代码:
used = set()
def get_inversions(S1):
inv = 0
for index, ch in enumerate(S1):
character = ord(ch)-ord('a')
cnt = sum(1 for x in range(character) if x not in used)
inv += cnt
used.add(character)
return inv
def f_recursive(n, S1, inv, ign):
if n == 0: return ""
delta = inv - (n-1)*(n-2)/2
if ign:
cnt = 0
ch = 0
else:
ch = ord(S1[len(S1)-n])-ord('a')
cnt = sum(1 for x in range(ch) if x not in used)
for letter in range(ch, len(S1)):
if letter not in used:
if cnt < delta:
cnt += 1
continue
used.add(letter)
if letter != ch: ign = True
return chr(letter+ord('a'))+f_recursive(n-1, S1, inv-cnt, ign)
def F_inv(S1):
used.clear()
inv = get_inversions(S1)
used.clear()
return f_recursive(len(S1), S1, inv+1, False)
print F_inv("acb")
也可以通过用binary indexed tree等数据结构替换最里面的循环来在O(n log n)
中运行。
答案 3 :(得分:-1)
您是否尝试在字符串中交换两个邻居字符?它似乎可以帮助解决问题。如果你交换S [i]和S [j],其中i&lt; j和S [i]&lt; S [j],则F(S)增加1,因为所有其他指数对不受此排列的影响。
如果我没弄错的话,F计算排列inversions的数量。