Como verificar se duas strings são permutações uma da outra usando espaço adicional O (1)?

13

Dadas duas strings, como você pode verificar se elas são permutadas uma da outra usando o espaço O (1)? Modificar as strings não é permitido de forma alguma.
Nota: O (1) espaço em relação ao comprimento da string E ao tamanho do alfabeto.

Anônimo
fonte
3
O que você acha? O que você tentou e onde ficou preso? As strings estão sobre um alfabeto de tamanho constante? Você já tentou calcular os histogramas?
Yuval Filmus
@YuvalFilmus deve ser o espaço O (1), tanto para o comprimento da string quanto para o tamanho do alfabeto #
197
Isso parece claramente impossível. Qualquer algoritmo exigirá espaço adicional para armazenar pelo menos uma posição em uma sequência ou em um único caractere. Nenhuma dessas coisas é O (1).
David Schwartz
@DavidSchwartz - como? O (1) significa constante, não um bute. Não importa quanto tempo a string tenha, sua posição é um número.
Davor
Depende do modelo da máquina, obviamente não há problema em modelos uniformes. Em um modelo de custo logarítmico, o índice é O(log n)para cadeias de comprimento n que não são constantes por meio do comprimento nem do tamanho do alfabeto. Quando as strings podem ser modificadas temporariamente, acho que há uma solução com alfabeto aumentado, que é linear no tamanho do alfabeto, mas constante no comprimento da string em um modelo logarítmico.
kap

Respostas:

7

A abordagem ingênua seria construir histogramas de ambas as cadeias e verificar se são iguais. Como não temos permissão para armazenar uma estrutura de dados (cujo tamanho seria linear ao tamanho do alfabeto) que poderia ser computado em uma passagem, precisamos contar as ocorrências de cada símbolo possível após a outra:

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

Obviamente, isso pressupõe que as contagens e os índices do iterador são números inteiros de tamanho constante, em vez de depender do comprimento das strings.

Bergi
fonte
Como otimização, você pode passar por uma matriz e calcular apenas os histogramas das letras que encontrar. Dessa maneira, a complexidade se torna independente do tamanho do alfabeto.
Yuval Filmus
Para expandir o comentário do @YuvalFilmus, você também precisa 1) verificar se os comprimentos das strings são iguais ou 2) iterar nas duas strings de entrada. Você precisa de um deles, pois é possível que algumas letras em um não estejam no outro. A opção 1 deve ter menos cálculos.
BurnsBA
@YuvalFilmus Eu queria evitar que, como isso significaria complexidade de tempo quadrático, eu esperaria que o alfabeto fosse menor que o tamanho médio da string. Para cadeias pequenas e um alfabeto ordenado, eu consideraria computar o próximo menor símbolo presente junto com a contagem no loop interno, para que você possa pular algumas iterações do loop do alfabeto - com uma complexidade de O(n * min(n, |Σ|)). Hm, agora que penso nisso, isso soa como a solução "permitida a repetição" da sua resposta, não é?
Bergi
countnão é O(1)(ou seja, este pode transbordar)
reinierpost
1
@Eternalcode Eu nunca disse que countera umint :-) Sim, não seria trabalho, mas em Java que não pode acontecer de qualquer maneira
Bergi
12

Denote as matrizes por e suponha que elas tenham comprimento n .A,Bn

Suponha primeiro que os valores em cada matriz sejam distintos. Aqui está um algoritmo que usa espaço :O(1)

  1. Calcule os valores mínimos de ambas as matrizes e verifique se são idênticas.

  2. Calcule os segundos valores mínimos de ambas as matrizes e verifique se são idênticos.

  3. E assim por diante.

A computação do valor mínimo de uma matriz usa claramente o espaço . Dado o k é o menor elemento, podemos encontrar o ( k +O(1)k menor elemento encontrando o valor mínimo maior que o k é o menor elemento (aqui usamos o fato de que todos os elementos são distintos).(k+1)k

Quando é permitido repetir elementos, modificamos o algoritmo da seguinte maneira:

  1. Calcula-se o mínimo de valores de ambas as matrizes, a contagem de quantas vezes cada aparecem, e verificar o m A , 1 = mmA,1,mB,1 e que as contagens são idênticos.mA,1=mB,1

  2. Calcule os valores mínimos maiores que m A , 1 , m B , 1 nas duas matrizes (respectivamente) e conte quantas vezes cada um aparece. Verifique se m A , 2 = mmA,2,mB,2mA,1,mB,1 e se as contagens são idênticas.mA,2=mB,2

  3. E assim por diante.

Yuval Filmus
fonte
1
Essa abordagem seria pois parece que a única maneira de encontrar o elemento min no espaço O ( 1 ) e o acesso somente leitura à matriz é iterar sobre todos os elementos? O(n2)O(1)
ryan
4
Isso requer uma ordem no alfabeto, embora seja fácil alterar o algoritmo para não exigir isso. No entanto, no caso "tem duplicatas", isso requer espaço , não O ( 1 ) . Contar ocupa espaço. O(lgn)O(1)
Derek Elkins saiu de SE
7
A contagem precisa de espaço (logarítmico), mas - por essa definição de uso do espaço -, ele também itera sobre a matriz. Assim, sob o significado estrito do uso do espaço, não há como fazê-lo no espaço constante.
Daniel Jour
4
@ DanielJour, depende de qual modelo de custo você está usando. Sob custo uniforme, isso é possível em espaço constante.
ryan
7
Se você só tiver permissão para um número constante de bits, poderá lidar apenas com alfabetos de tamanho constante (isso segue a teoria das linguagens regulares).
Yuval Filmus
2

Defina alguma função f (c) que mapeia algum caractere c para um número primo exclusivo (a = 2, b = 3, c = 5, etc.).

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

Apenas declarar que você pode usar uma função de mapeamento de números primos é um pouco ondulado e provavelmente onde um problema surgiria mantendo espaço O (1) .O(1)

Alex Stasse
fonte
Com um limite no alfabeto, deve usar O ( 1 ) espaço, caso contrário, acredito que não seria espaço constante. Além disso, se você o computasse no espaço O ( 1 ) , seria extremamente ineficiente com base nos resultados atuais . Ainda assim, +1 para a abordagem da primalidade. f(c)O(1)O(1)
ryan
Outro problema que percebi após a postagem é que a soma de verificação será um número gigantesco para grandes seqüências, na medida em que por si só possa violar o requisito de espaço O (1). Isso pode ser resolvido usando floats e mutliplicando por um caractere em uma string e depois dividindo na outra, apenas dizendo que a soma de verificação deve estar próxima de 1. As strings teriam que ser verdadeiramente gigantescas para que o erro de ponto flutuante fosse um problema.
Alex Stasse # 8/17
4
O(logn)
4
Θ(n)n
0

Você pode fazer isso é O(nlogn). Classifique as duas cadeias e compare-as índice por índice. Se eles diferem em qualquer lugar, não são permutações um do outro.

Para uma O(n)solução, o hash pode ser usado. Essa função de hash funcionaria e, epara qualquer letra, seria seu valor ascii. Se os dois hashes das strings diferirem, eles não são permutações um do outro.

A função de hash no link:

Um candidato em potencial pode ser esse. Corrija um número inteiro ímpar R. Para cada elemento e você deseja calcular o hash do fator (R + 2 * e). Em seguida, calcule o produto de todos esses fatores. Por fim, divida o produto por 2 para obter o hash.

O fator 2 em (R + 2e) garante que todos os fatores são ímpares, evitando assim que o produto se torne 0. A divisão por 2 no final é porque o produto sempre será ímpar, portanto a divisão apenas remove um bit constante .

Por exemplo, eu escolhi R = 1779033703. Essa é uma escolha arbitrária, fazendo alguns experimentos devem mostrar se um determinado R é bom ou ruim. Suponha que seus valores sejam [1, 10, 3, 18]. O produto (calculado usando entradas de 32 bits) é

(R + 2) * (R + 20) * (R + 6) * (R + 36) = 3376724311 Portanto, o hash seria

3376724311/2 = 1688362155.

O uso de hash duplo (ou para exagero ainda mais) alterando o valor de R os identificaria com sucesso como permutações com probabilidade muito alta .

Em dúvida
fonte
1
Você não pode classificar as cadeias, pois não tem permissão para modificá-las. Quanto ao hash, é um algoritmo aleatório que pode dar a resposta errada.
Yuval Filmus
0

Digamos que você tenha duas strings chamadas s e t.

Você pode usar heurísticas para garantir que elas não sejam desiguais.

  1. s.length == t.length
  2. soma de caracteres de s == soma de caracteres em t
  3. [o mesmo que em 2. mas com xor em vez de soma]

Depois disso, você pode executar facilmente um algoritmo para provar que a string é igual.

  1. classifique uma string para ser igual à outra e compare (O (n ^ 2))
  2. classifique ambos e compare (O (2n log (n))
  3. verifique cada caractere em s se houver as mesmas quantidades nas duas strings (O (n ^ 2))

É claro que você não pode classificar tão rápido se não tiver permissão para usar espaço adicional. Portanto, não importa qual algoritmo você escolher - cada algoritmo precisará será executado em O (n ^ 2) quando houver apenas O (1) espaço e se a heurística não puder provar que eles não podem ser iguais.

MurksVomOrk
fonte
3
" Modificar as strings não é permitido de forma alguma. "
Bergi
0

No código de estilo C para toda a rotina:

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; 

Ou no pseudo-código muito detalhado (usando a indexação baseada em 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

onde a função checkLetters (A, B, i) verifica se existem M cópias de A [i] em A [1] .. A [i], existem pelo menos M cópias de A [i] em B:

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

e a função findNextValue procura em B um valor iniciando em um índice e retorna o índice onde foi encontrado (ou n + 1, se não encontrado).

n2

MotiN
fonte
Você pode converter seu código C em pseudocódigo? Este não é um site de programação.
Yuval Filmus
Isso parece ser outra variante da resposta de Bergi (com algumas diferenças inconseqüentes).
Yuval Filmus
É semelhante, mas não uma variante. A resposta de Bergi éO(nm)onde m = tamanho do alfabeto. Isto éO(n2).
Motin
0

Eu acho que esse é o algoritmo mais simples (com O(n3) Tempo, n comprimento das cordas)

Percorra string1e string2, para cada personagem, verifique com que frequência ele pode ser encontrado em string1e string2. Se um personagem está mais freqüentemente em uma sequência do que na outra, não é uma permutação. Se as frequências de todos os caracteres forem iguais, as cordas são permutações uma da outra.

Aqui está um pedaço de python para tornar isso preciso

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)

O programa precisa de alguns ponteiros para cordas ( string, string1, string2, char, char1, char2) e variáveis de tamanhoO(registron)para contar ( count1, count2). Ele precisa verificar se os caracteres são iguais ou não, mas a Dows não precisa de nenhuma ordem nesses caracteres. Talvez seja necessário algumas variáveis ​​para números inteiros pequenos (por exemplo, para manter valores booleanos ou para representar a posição de stringin [string1, string2] .

Claro que você nem precisa das variáveis ​​de contagem, mas pode usar ponteiros.

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)

Este segundo programa precisa de variáveis ​​semelhantes às do primeiro, exceto que não precisa do O(registro(n))variáveis ​​de tamanho para manter os valores de contagem.

Portanto, na verdade, não depende de n ou o tamanho do alfabeto.

miracle173
fonte
É o mesmo que a solução da Bergi abaixo.
Yuval Filmus
@YuvalFilmus Não, ele não repete todo o alfabeto e, portanto, seu tempo de execução não depende do tamanho do alfabeto. Ele usa apenas as duas seqüências que devem ser testadas. Além disso, o segundo programa evita a contagem.
miracle173
@YuvalFilmus Vejo agora que seus e outros comentários apontam na direção da maneira como usei no meu programa.
precisa saber é o seguinte