如何使用O(1)额外空间检查两个字符串是否彼此置换?


13

给定两个字符串,如何使用O(1)空间检查它们是否彼此置换?不允许以任何方式修改字符串。
注意:与字符串长度和字母大小有关的O(1)空间。


3
你怎么看?您尝试了什么,在哪里被卡住?字符串是否在恒定大小的字母上?您是否尝试过计算他们的直方图?
Yuval Filmus

@YuvalFilmus字符串的长度和字母的大小都应为O(1)空间
匿名

这显然是不可能的。任何算法都将需要额外的空间以在一个字符串或单个字符中至少存储一个位置。这些都不是O(1)。
David Schwartz

@DavidSchwartz-怎么样?O(1)表示常数,而不是一个常数。字符串的长度无所谓,其中的位置是一个数字。
达沃

这取决于机器模型,在统一模型中显然没有问题。在对数成本模型中,存储的索引是O(log n)长度为n的字符串,长度既不取决于长度也不取决于字母大小。当可以临时修改字符串时,我认为存在一种解决方案,可以增加字母,字母大小是线性的,而对数模型中的字符串长度是恒定的。
kap

Answers:


7

天真的方法是建立两个字符串的直方图并检查它们是否相同。由于不允许我们存储这样的数据结构(其大小将与字母的大小成线性关系),可以一次计算出该数据结构,因此我们需要在另一个之后计算每个可能符号的出现次数:

function count(letter, string)
    var count := 0
    foreach element in string
        if letter = element
            count++
    return count

function samePermutation(stringA, stringB)
    foreach s in alphabet
        if count(s, stringA) != count(s, stringB)
            return false
    return true

当然,这确实假定count和iterator索引是恒定大小的整数,而不是取决于字符串的长度。


作为一种优化,您可以遍历一个数组,仅计算遇到的字母的直方图。这样,复杂度变得与字母大小无关。
Yuval Filmus

要扩展@YuvalFilmus注释,您还需要1)检查字符串长度是否相同或2)遍历两个输入字符串。您需要其中之一,因为一个字母中的某些字母可能不在另一个字母中。选项1的计算应更少。
BurnsBA

@YuvalFilmus我想避免这种情况,因为这将意味着二次时间复杂度,所以我希望字母表小于平均字符串大小。对于较小的字符串和有序的字母,我会考虑计算下一个最小的当前符号以及内部循环中的计数,以便可以跳过字母表循环的几次迭代-的复杂度为O(n * min(n, |Σ|))。嗯,现在我考虑一下,这听起来很像您回答中的“允许重复”解决方案,不是吗?
Bergi

count不是O(1)(即可能会溢出)
reinierpost

1
@Eternalcode我从未说过这count是一个int:-)是的,它不会工作,但是在Java 无论如何都不会发生
Bergi

12

表示数组,并假设它们的长度为nA,Bn

首先假设每个数组中的值都是不同的。这是使用空间的算法:O(1)

  1. 计算两个数组的最小值,并检查它们是否相同。

  2. 计算两个数组的第二个最小值,并检查它们是否相同。

  3. 等等。

计算数组的最小值显然会占用空间。鉴于ķ个最小的元素,我们可以找到ķ + 1 ), ST最小通过寻找最低值都比较大元素ķ个最小的元素(在这里我们使用的事实,所有的元素都是不同的)。O(1)k(k+1)k

当允许重复元素时,我们将算法修改如下:

  1. mA,1,mB,1mA,1=mB,1

  2. mA,2,mB,2mA,1,mB,1mA,2=mB,2

  3. 等等。


1
O(n2)O(1)

4
O(lgn)O(1)

7
计数确实需要(对数)空间,但是-根据空间使用的定义-甚至遍历数组也是如此。因此,在严格意义上的空间使用意义上,没有办法在恒定空间中做到这一点。
Daniel Jour

4
@DanielJour,这取决于您使用的成本模型。在统一成本下,这可以在恒定空间内进行。
瑞安

7
如果只允许固定数量的位,则只能处理恒定大小的字母(这是根据常规语言的理论得出的)。
Yuval Filmus

2

定义一些函数f(c),它将某些字符c映射到唯一的质数(a = 2,b = 3,c = 5,等等)。

set checksum = 1
set count = 0 <-- this is probably not even necessary, but it's another level of check
for character c in string 1
    checksum = checksum * f(c)
    count = count + 1
for character c in string 2
    checksum = checksum / f(c)
    count = count = 1

permutation = count == 0 and checksum == 1

O(1)


f(c)O(1)O(1)

发布后我意识到的另一个问题是,对于大字符串,校验和将是一个巨大的数字,以至于它本身可能会违反O(1)空间要求。这可以通过以下方式解决:使用浮点数并在一个字符串上用一个字符对它们进行互斥,然后在另一个字符串上除以,然后只说校验和必须接近于 1。对于浮点错误成为问题,字符串必须确实是巨大的。
亚历克斯·斯塔斯

4
O(logn)

4
Θ(n)n

0

您可以做到的是O(nlogn)。对两个字符串进行排序,然后按索引对它们进行比较。如果它们在任何地方都不同,则它们并不是彼此置换。

对于O(n)解决方案,可以使用哈希。哈希函数将起作用,并且 e对于任何字母将为其ascii值。如果字符串的两个哈希不同,则它们不是彼此置换。

链接中的哈希函数:

一个潜在的候选人可能就是这个。固定一个奇数R。对于每个要散列的元素e,计算因子(R + 2 * e)。然后计算所有这些因素的乘积。最后将乘积除以2得到哈希值。

(R + 2e)中的因数2保证所有因数均为奇数,因此避免了乘积将变为0。最后除以2是因为乘积将始终为奇数,因此除法仅除去了一个恒定位。

例如,我选择R =1779033703。这是一个任意选择,做一些实验应该表明给定的R是好是坏。假设您的值为[1、10、3、18]。乘积(使用32位整数计算)为

(R + 2)*(R + 20)*(R + 6)*(R + 36)= 3376724311因此哈希将为

3376724311/2 = 1688362155。

通过更改R的值使用双哈希(甚至更多用于过度杀伤)将成功地将其识别为排列的可能性很高


1
您不能对字符串进行排序,因为不允许您对其进行修改。至于散列,这是一种随机算法,可能给出错误的答案。
Yuval Filmus

0

假设您有两个名为s和t的字符串。

您可以使用试探法确保它们不相等。

  1. s.length == t.length
  2. s的字符总数== t中的字符总数
  3. [与2中相同。但用xor代替sum]

之后,您可以轻松地运行算法以证明字符串相等。

  1. 将一个字符串排序为与另一个字符串相等,然后进行比较(O(n ^ 2))
  2. 对两者进行排序并进行比较(O(2n log(n))
  3. 检查s中的每个字符,如果两个字符串中的数量相同(O(n ^ 2))

当然,如果不允许使用额外的空间,则无法快速排序。因此,选择哪种算法都没有关系-当只有O(1)空间并且启发式方法无法证明它们不相等时,每种算法将需要O(n ^ 2)时间运行。


3
禁止以任何方式修改字符串。
Bergi

0

在整个例程的C风格代码中:

for (int i = 0; i < n; i++) {
   int k = -1;
   next: for (int j = 0; j <= i; j++)
       if (A[j] == A[i]) {
          while (++k < n)
              if (B[k] == A[i])
                  continue next;
          return false; // note at this point j == i
       }
}
return true; 

或使用非常冗长的伪代码(使用基于1的索引)

// our loop invariant is that B contains a permutation of the letters
// in A[1]..A[i-1]
for i=1..n
   if !checkLetters(A, B, i)
      return false
return true

其中,函数checkLetters(A,B,i)检查A [1] .. A [i]中是否有M个A [i]副本,然后B中至少有M个A [i]副本:

checkLetters(A,B,i)
    k = 0 // scan index into B
    for j=1..i
      if A[j] = A[i]
         k = findNextValue(B, k+1, A[i])
         if k > n
            return false
    return true

函数findNextValue在B中搜索从索引开始的值,并返回找到它的索引(如果找不到,则返回n + 1)。

n2


您可以将C代码转换为伪代码吗?这不是编程站点。
Yuval Filmus

这似乎是Bergi答案的另一个变体(有一些无关紧要的区别)。
Yuval Filmus

O(nm)O(n2)

0

O(n3n

遍历string1string2,对每个字符检查在string1和中可以找到它的频率string2。如果一个字符比另一个字符更经常出现在一个字符串中,那不是排列。如果所有字符的频率相等,则字符串是彼此排列的。

这是一段使这个精确的python

s1="abcaba"
s2="aadbba"

def check_if_permutations(string1, string2):
  for string in [string1, string2]:
    # string references string1 
    #  string2, it is not a copy
    for char in string:
      count1=0
      for char1 in string1:
        if  char==char1:
          count1+=1
      count2=0
      for char2 in string2:
        if  char==char2:
          count2+=1
      if count1!=count2:
        print('unbalanced character',char)
        return()
  print ("permutations")
  return()

check_if_permutations(s1,s2)

stringstring1string2charchar1char2O(logn)count1count2string[string1, string2]

当然,您甚至不需要count变量,但可以使用指针。

s1="abcaba"
s2="aadbba"

def check_if_permutations(string1, string2):
  for string in [string1, string2]:
    # string references one of string1 
    # or string2, it is not a copy
    for char in string:
      # p1 and p2 should be views as pointers
      p1=0
      p2=0
      while (p1<len(string1)) and (p2<len(string2)):
        # p1>=len(string1): p1 points to beyond end of string
        while (p1<len(string1)) and (string1[p1]!=char) :
          p1+=1
        while(p2<len(string2)) and (string2[p2]!=char):
          p2+=1
        if (p1<len(string1)) != (p2<len(string2)):
          print('unbalanced character',char)
          return()
        p1+=1
        p2+=1
  print ("permutations")
  return()

check_if_permutations(s1,s2)

O(log(n))

n


这与下面的Bergi解决方案相同。
Yuval Filmus

@YuvalFilmus不,它不会遍历整个字母,因此其运行时间不取决于字母大小。它仅使用应该测试的两个字符串。第二个程序也避免计数。
miracle173 '17

我现在看到@YuvalFilmus,您的注释和其他注释都指向我在程序中使用的方式。
miracle173 '17
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.