字符串词典排列和反转

时间:2012-06-12 08:57:33

标签: string algorithm permutation

在字符串上考虑以下函数:

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?

4 个答案:

答案 0 :(得分:0)

假设S1的长度为n,F(S1)的最大值为n(n-1)/2,如果F(S1) = n(n-1)/2,则表示它是最后一个函数,并且没有任何下一个函数,但是如果{ {1}},表示至少有一个字符F(S1) < n(n-1)/2大于字符xy位于x旁边,找到这样的y索引最低,并更改x和y位置。让我们看看它的例子:

S1 ==“acb”(F = 1),1&lt; 3所以有一个char x比另一个char x大,而且它的索引大于y,这里最小的索引yx,并且首先尝试将其替换为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的倒数不是函数,因为它是多值的,但是给定nF(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的数量。