【问题标题】:String lexicographical permutation and inversion字符串字典排列和倒置
【发布时间】:2012-06-12 08:57:32
【问题描述】:

考虑以下对字符串的函数:

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?

【问题讨论】:

  • 当您的输入字符串是最后一个排列时,您期望得到什么答案? IE。如果在您的示例中 S1 = "cba",那么 S2 应该是什么?
  • 你可以假设 S1 永远不会是最后一个元素

标签: string algorithm permutation


【解决方案1】:

假设S1的长度为n,F(S1)的最大值为n(n-1)/2,如果F(S1) = n(n-1)/2,表示它是最后一个函数,没有下一个,但如果F(S1) &lt; n(n-1)/2,表示有y旁边至少有一个大于char yx的char x,找到这样一个索引最低的x,并更改x和y位置。让我们通过例子来看看:

S1 == "acb" (F = 1) , 1 x 大于另一个字符 y 并且它的索引大于y,这里最小索引@987654333 @ 是c,第一次尝试时,您将用a 替换它(小于x,因此算法在这里结束)==> S2= "cab", F(S2) = 2。

现在让我们用 S2 进行测试,cab: x=b, y=a, ==> S3 = "cba"。\

找到x并不难,迭代输入,变量命名为min,而当前访问的字符小于min,设置min为新访问的字符,并访问下一个字符,第一次访问大于min的字符时停止迭代,这是x

这是 c# 中的伪代码(但我不注意边界,例如在 input.Substring 中):

string NextString(string input)
{
    var min = input[0];
    int i=1;
    while (i < input.Length && input[i] < min)
    {
       min = input[i];
       i++;
    }

    if (i == input.Length) return "There isn't next item";

    var x = input[i], y=input[i-1];
    return input.Substring(0,i-2) + x + y + input.Substring(i,input.Length - 1 - i);

}

【讨论】:

  • @ffao, F("bca") = F("cab") = 2,请参阅 OP 示例。
  • 我们想在集合中找到 next 字符串 S2... 另外,在最后一行: if S1 == "acb" (F = 1) than S2 == "bca"
【解决方案2】:

这是解决问题的算法大纲。

我假设您有一个函数可以直接返回n-th 排列(给定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 个字母时,您将获得 F 值表的 4 个副本,分别将 (0,1,2,3) 添加到 (1st,2nd,3rd,4th) 副本。例如,在第 2 种情况下,您已经将第 2 个字母放在第 1 位,从而导致一种错乱;这只是以与原始 3 字母字符串相同的模式添加到其他混乱中。

从这个大纲来看,编写函数 F 应该不会太难(但我现在没有时间)。严格来说,F 的逆函数不是一个函数,因为它是多值的,但是给定nF(n),只有少数情况下可以找到m st F(m)==F(n)+1。这些案例是:

  • n == N! 其中N是字符串中的字母个数,没有下一个排列;
  • F(n+1) &lt; F(n),寻求的解决方案是perm(n+(N-1)!),;
  • F(n+1) == F(n),解决方法是perm(n+2)
  • F(n+1) &gt; F(n),解决方法是perm(n+1)

我怀疑其中一些可能只适用于 4 个字母的字符串,其中一些术语必须针对 K 字母排列进行调整。

【讨论】:

    【解决方案3】:

    这不是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 时很有用。您想知道第一个按字典顺序较小的字符串 - 因此,复制原始字符串并仅在强制时更改它是有意义的.当需要进行此类更改时,我们还应该尽可能少地更改字符串。

    为此,让我们使用具有n 字母的字符串的最大F 值为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) 中运行。

    【讨论】:

      【解决方案4】:

      您是否尝试交换字符串中的两个相邻字符?似乎它可以帮助解决问题。如果交换 S[i] 和 S[j],其中 i

      如果我没记错的话,F 会计算排列的inversions 的个数。

      【讨论】:

      • 考虑 abcd,它有 0 个反转,如果我交换 a 和 d,它变成 dbca 并且有 5 个反转。这是增加了五个,而不是一个。
      • 在该示例中,a 和 d 不是相邻字符。
      • F("acb") = 1 的答案是 F("bca") = 2。不能通过交换邻居从 "acb" 到达 "bca"。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2016-03-04
      • 2020-04-30
      • 1970-01-01
      • 2014-06-07
      相关资源
      最近更新 更多