Come verificare se due stringhe sono permutazioni l'una dell'altra utilizzando O (1) spazio aggiuntivo?


13

Date due stringhe come puoi verificare se sono una permutazione l'una dell'altra usando lo spazio O (1)? La modifica delle stringhe non è consentita in alcun modo.
Nota: O (1) spazio in relazione sia alla lunghezza della stringa che alla dimensione dell'alfabeto.


3
Cosa pensi? Cosa hai provato e dove ti sei bloccato? Le stringhe sono sopra un alfabeto di dimensioni costanti? Hai provato a calcolare i loro istogrammi?
Yuval Filmus,

@YuvalFilmus dovrebbe essere O (1) spazio sia per la lunghezza della stringa che per la dimensione dell'alfabeto
Anonimo

Questo sembra chiaramente impossibile. Qualsiasi algoritmo richiederà spazio aggiuntivo per memorizzare almeno una posizione in una stringa o in un singolo carattere. Nessuna di queste cose è O (1).
David Schwartz,

@DavidSchwartz - come? O (1) significa costante, non un bute. Non importa quanto sia lunga la stringa, la posizione in essa è un numero.
Assapora l'

Dipende dal modello della macchina, ovviamente nessun problema nei modelli uniformi. In un modello di costo logaritmico la memorizzazione dell'indice è O(log n)per stringhe di lunghezza n che non è né costante per lunghezza né per dimensione dell'alfabeto. Quando le stringhe possono essere temporaneamente modificate, penso che esista una soluzione con alfabeto aumentato che sia lineare nella dimensione dell'alfabeto ma costante nella lunghezza della stringa in un modello logaritmico.
Kap l'

Risposte:


7

L'approccio ingenuo sarebbe costruire istogrammi di entrambe le stringhe e verificare se sono uguali. Poiché non ci è consentito memorizzare una tale struttura di dati (la cui dimensione sarebbe lineare rispetto alla dimensione dell'alfabeto) che potrebbe essere calcolata in un passaggio, dobbiamo contare le occorrenze di ogni possibile simbolo dopo l'altro:

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

Ciò presuppone ovviamente che i conteggi e gli indici iteratori siano numeri interi di dimensione costante, anziché dipendere dalla lunghezza delle stringhe.


Come ottimizzazione, puoi andare oltre un array e calcolare solo gli istogrammi delle lettere che incontri. In questo modo la complessità diventa indipendente dalla dimensione dell'alfabeto.
Yuval Filmus,

Per espandere il commento di @YuvalFilmus, è necessario anche 1) verificare che le lunghezze delle stringhe siano uguali o 2) iterare su entrambe le stringhe di input. È necessario uno di questi in quanto è possibile che alcune lettere in una non siano nell'altra. L'opzione 1 dovrebbe avere meno calcoli.
BurnsBA

@YuvalFilmus Volevo evitare che, dato che avrebbe significato una complessità temporale quadratica, mi sarei aspettato che l'alfabeto fosse più piccolo della dimensione media della stringa. Per stringhe piccole e un alfabeto ordinato, prenderei in considerazione il calcolo del prossimo simbolo presente più piccolo insieme al conteggio nel ciclo interno, in modo da poter saltare alcune iterazioni del ciclo dell'alfabeto - con una complessità di O(n * min(n, |Σ|)). Hm, ora che ci penso, sembra una soluzione "autorizzata a ripetere" dalla tua risposta, vero?
Bergi,

countnon è O(1)(cioè potrebbe traboccare)
reinierpost l'

1
@Eternalcode Non ho mai detto che countfosse un int:-) Sì, non avrebbe funzionato, ma in Java comunque non può succedere
Bergi

12

Indica le matrici con e supponi che siano di lunghezza n .UN,Bn

Supponiamo innanzitutto che i valori in ciascun array siano distinti. Ecco un algoritmo che utilizza lo spazio :O(1)

  1. Calcola i valori minimi di entrambi gli array e verifica che siano identici.

  2. Calcola i secondi valori minimi di entrambi gli array e verifica che siano identici.

  3. E così via.

Il calcolo del valore minimo di un array utilizza chiaramente lo spazio . Dato il k ° elemento più piccolo, possiamo trovare l'elemento ( k + 1 ) più piccolo trovando il valore minimo più grande dell'elemento k k più piccolo (qui usiamo il fatto che tutti gli elementi sono distinti).O(1)K(K+1)K

Quando gli elementi possono ripetere, modifichiamo l'algoritmo come segue:

  1. Calcola i valori minimi di entrambi gli array, conta quante volte appaiono ciascuna e verifica m A , 1 = m B , 1 e che i conteggi siano identici.mA,1,mB,1mA,1=mB,1

  2. Calcola i valori minimi maggiori di m A , 1 , m B , 1 nei due array (rispettivamente) e conta quante volte appaiono ciascuna. Verificare che m A , 2 = m B , 2 e che i conteggi siano identici.mA,2,mB,2mA,1,mB,1mA,2=mB,2

  3. E così via.


1
Questo approccio sarebbe poiché sembra che l'unico modo per trovare l' elemento min nello spazio O ( 1 ) e l'accesso in sola lettura all'array sia iterare su tutti gli elementi? O(n2)O(1)
Ryan,

4
Ciò richiede un ordinamento sull'alfabeto, sebbene sia facile cambiare l'algoritmo per non richiederlo. Tuttavia, nel caso "ha duplicati", questo richiede spazio, non O ( 1 ) . Il conteggio richiede spazio. O(lgn)O(1)
Derek Elkins lasciò SE l'

7
Il conteggio ha bisogno di spazio (logaritmico), ma - con questa definizione di utilizzo dello spazio - lo fa anche iterando sull'array. Pertanto, sotto lo stretto significato dell'uso dello spazio, non c'è modo di farlo in uno spazio costante.
Daniel Jour,

4
@DanielJour, dipende dal modello di costo che stai utilizzando. A costi uniformi, questo è possibile in uno spazio costante.
Ryan,

7
Se ti è permesso solo un numero costante di bit, puoi gestire solo alfabeti di dimensioni costanti (questo segue dalla teoria dei linguaggi regolari).
Yuval Filmus,

2

Definire alcune funzioni f (c) che mappano alcuni caratteri c su un numero primo univoco (a = 2, b = 3, c = 5, ecc.).

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

Dichiarare che è possibile utilizzare una funzione di mappatura dei numeri primi è un po 'complicato, e molto probabilmente dove sorgerebbe un problema mantenendo lo spazio .O(1)


Con un limite sull'alfabeto, dovrebbe usare lo spazio O ( 1 ) , altrimenti credo che non sarebbe spazio costante. Inoltre, se lo calcolassi nello spazio O ( 1 ) , sarebbe estremamente inefficiente in base ai risultati attuali . Tuttavia, +1 per l'approccio di primalità. f(c)O(1)O(1)
Ryan,

Un altro problema che ho capito dopo la pubblicazione è che il checksum sarà un numero gigantesco per stringhe di grandi dimensioni, nella misura in cui da solo potrebbe violare il requisito di spazio O (1). Questo può essere risolto usando float e mutliplying di un carattere su una stringa e poi dividendoli sull'altra, quindi dicendo semplicemente che il checksum deve essere vicino a 1. Le stringhe dovrebbero essere davvero gigantesche perché l'errore in virgola mobile sia un problema.
Alex Stasse,

4
O(logn)

4
Θ(n)n

0

Puoi farlo O(nlogn). Ordina le due stringhe e confrontale per indice. Se differiscono ovunque, non sono permutazioni l'una dell'altra.

Per una O(n)soluzione, è possibile utilizzare l'hash. Questa funzione di hashing avrebbe funzionato, e eper ogni lettera sarebbe il suo valore ASCII. Se i due hash delle stringhe differiscono, non sono permutazioni l'una dell'altra.

La funzione di hashing nel link:

Un potenziale candidato potrebbe essere questo. Correggi un numero intero dispari R. Per ogni elemento e vuoi calcolare l'hash del fattore (R + 2 * e). Quindi calcola il prodotto di tutti questi fattori. Dividi infine il prodotto per 2 per ottenere l'hash.

Il fattore 2 in (R + 2e) garantisce che tutti i fattori siano dispari, evitando così che il prodotto diventerà mai 0. La divisione per 2 alla fine è perché il prodotto sarà sempre dispari, quindi la divisione rimuove solo un bit costante .

Ad esempio, scelgo R = 1779033703. Questa è una scelta arbitraria, fare alcuni esperimenti dovrebbe mostrare se una data R è buona o cattiva. Supponi che i tuoi valori siano [1, 10, 3, 18]. Il prodotto (calcolato utilizzando ints a 32 bit) è

(R + 2) * (R + 20) * (R + 6) * (R + 36) = 3376724311 Quindi l'hash sarebbe

3376724311/2 = 1688362155.

L'uso del doppio hashing (o ancora per eccesso) modificando il valore di R li identificherebbe con successo come permutazioni con probabilità molto alta .


1
Non è possibile ordinare le stringhe poiché non è consentito modificarle. Per quanto riguarda l'hashing, è un algoritmo randomizzato che potrebbe dare la risposta sbagliata.
Yuval Filmus,

0

Supponiamo che tu abbia due stringhe chiamate s e t.

È possibile utilizzare l'euristica per assicurarsi che non siano disuguali.

  1. s.length == t.length
  2. somma dei caratteri di s == somma dei caratteri in t
  3. [lo stesso di 2. ma con xor invece di somma]

Dopodiché puoi facilmente eseguire un algoritmo per dimostrare che la stringa è uguale.

  1. ordina una stringa in modo che sia uguale all'altra e confronta (O (n ^ 2))
  2. ordina entrambi e confronta (O (2n log (n))
  3. controlla per ogni carattere in s se ci sono gli stessi importi in entrambe le stringhe (O (n ^ 2))

Ovviamente non puoi ordinare così velocemente se non ti è permesso usare spazio aggiuntivo. Quindi non importa quale algoritmo si scelga: ogni algoritmo di cui avrà bisogno verrà eseguito nel tempo O (n ^ 2) quando c'è solo O (1) spazio e se l'euristica non è stata in grado di dimostrare che non possono essere uguali.


3
"La modifica delle stringhe non è consentita in alcun modo. "
Bergi,

0

Nel codice in stile C per l'intera routine:

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; 

O in pseudo codice molto dettagliato (usando l'indicizzazione basata su 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

dove la funzione checkLetters (A, B, i) controlla che se ci sono M copie di A [i] in A [1] .. A [i], allora ci sono almeno M copie di A [i] in 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 la funzione findNextValue cerca in B un valore a partire da un indice e restituisce l'indice in cui è stato trovato (o n + 1 se non trovato).

n2


Puoi per favore convertire il tuo codice C in pseudocodice? Questo non è un sito di programmazione.
Yuval Filmus,

Questa sembra un'altra variante della risposta di Bergi (con alcune differenze insignificanti).
Yuval Filmus,

O(nm)O(n2)

0

O(n3n

Passa attraverso string1e string2, per ogni personaggio, controlla quanto spesso può essere trovato in string1e string2. Io un personaggio è più spesso in una stringa che nell'altra, non è una permutazione. Se le frequenze di tutti i caratteri sono uguali, le stringhe sono permutazioni l'una dell'altra.

Ecco un pezzo di pitone per renderlo 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)

stringstring1string2charchar1char2O(logn)count1count2string[string1, string2]

Ovviamente non hai nemmeno bisogno delle variabili di conteggio ma puoi usare i puntatori.

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))

Quindi in realtà non dipende n o la dimensione dell'alfabeto.


Questo è lo stesso della soluzione di Bergi di seguito.
Yuval Filmus,

@YuvalFilmus No, non scorre sull'intero alfabeto e quindi il suo tempo di esecuzione non dipende dalla dimensione dell'alfabeto. Utilizza solo le due stringhe che devono essere testate. Anche il secondo programma evita il conteggio.
miracle173

@YuvalFilmus Vedo ora che i tuoi e altri commenti puntano nella direzione in cui ho usato il mio programma.
miracle173
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.