3 Stimmen

Anzahl der längsten gemeinsamen Teilfolgen

Ich muss die Anzahl der unterschiedlichen längsten gemeinsamen Teilfolgen zwischen zwei Zeichenfolgen A und B finden. Ich verwende derzeit den normalen dynamischen Programmierungsansatz und generiere dann alle unterschiedlichen Teilfolgen, indem ich ein Backtrack-Array verwende und dann eine Tiefensuche ab dem Startindex durchführe.

Da jedoch die Anzahl möglicher Antworten sehr hoch ist, ist mein Code zu langsam. Gibt es eine Möglichkeit, die Anzahl solcher unterschiedlichen längsten gemeinsamen Teilfolgen zu zählen, ohne sie tatsächlich zu generieren?

Bisheriger Code:

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.Arrays;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Stack;

class Node
{
String res = "";
int i;
int j;

public Node( int _i, int _j, String s )
{
    i = _i;
    j = _j;
    res = s;
}
}

public class LCSRevisited
{
static String a;
static String b;
static int m,n;
static int[][] memo;
static int[][] bt; // 1 bedeutet [i+1][j], 2 bedeutet [i][j+1], 3 bedeutet [i+1][j+1]
// 4 - bedeutet beides

static HashSet  filter;

static void printAllStrings( )
{
    Iterator i = filter.iterator();

    while( i.hasNext())
    {
        System.out.println( i.next() );
    }
} 

 static void printSol()
 {
   System.out.print( memo[ 0 ][ 0 ]);

   // Überprüfen, wie viele EINZIGARTIGE solche Zeichenfolgen existieren

   filter = new HashSet();
   Stack s = new Stack();
   Node start = new Node( 0, 0, "" );
   s.push( start );
   Node curr;
   String res;

   // Verwenden des Backtrack-Arrays, um eine Tiefensuche durchzuführen

   while( !s.isEmpty() )
   {
        curr = s.pop();
        res = curr.res;

        if( ( curr.i>=m) || ( curr.j >=n ) )
        {
            filter.add( curr.res);
            continue;
       }

        // Überprüfen des Backtrack-Wertes
        int i = curr.i;
        int j = curr.j;
        int back = bt[ i ][ j];

        if( back == 1 )
        {
            s.push( new Node( i+1, j, res ));
        }
        if( back == 2 )
        {
            s.push( new Node( i, j+1, res ));
        }
        if( back == 3 )
        {
            s.push( new Node( i+1, j+1, res+a.charAt(i) ));
        }
        if( back == 4 )
        {
            s.push( new Node( i, j+1, res ));
            s.push( new Node( i+1, j, res ));
        }
   }
   //printAllStrings();
   System.out.println(" " + filter.size() );
}

static void solve()
{
   // Basissituationen ausfüllen
   m = a.length();
   n = b.length();
   memo = new int[ m+1 ][ n+1 ];
   Arrays.fill( memo[m], 0 );

   bt = new int[ m+1 ][ n+1 ];

   for( int i=0; i=0; i-- )
   {
       for( int j=n-1; j>=0; j-- )
       {
           if( a.charAt(i) == b.charAt(j))
           {
               memo[ i ][ j ] = 1 + memo[ i+1 ][ j+1 ];
               bt[ i ][ j ] = 3;
           }
           else
           {
               int r1 = memo[ i+1 ][ j ];
               int r2 = memo[ i ][ j+1 ];

               if( r1==r2 )
               {
                    memo[ i ][ j ] = r1;
                    bt[ i ][ j ] = 4;
               }
               else if( r1 > r2 )
               {
                   memo[ i ][ j ] = r1;
                   bt[ i ][ j ] = 1;
               }
               else
               {
                   memo[ i ][ j ] = r2;
                   bt[ i ][ j ] = 2;
               }
           }
       }
   }

   printSol();
 }

public static void main( String[] args ) throws IOException
{
 BufferedReader br = new BufferedReader( new InputStreamReader( System.in ));

int T= Integer.parseInt( br.readLine() );

while( T--> 0 )
{
    a = br.readLine();
    b = br.readLine();

    if( T>=1 )
    br.readLine();

    solve();
    // printArr( bt );
}
}
}

1voto

Chris Punkte 26056

Ich denke, du kannst eine Rolling-Hash-Funktion wie Rabin Karp's verwenden. Auf diese Weise kannst du neue Hash-Werte längerer gemeinsamer Teilfolgen berechnen, ohne den gesamten String erneut generieren und hashen zu müssen.

Eigentlich denke ich, du kannst reine DP verwenden, um deine Antwort zu finden. Angenommen, du hast bereits die Werte der DP-Tabelle für LCS berechnet (memo[][] in deinem Code, denke ich). Dann kannst du die Anzahl der unterschiedlichen LCS-Instanzen wie folgt berechnen

for j von 0 bis n do
    for i von 0 bis m do
        if i = 0 oder j = 0 dann
            D[i, j] = 1
        else
            D[i, j] = 0
            if ai = bj dann
                D[i, j] = D[i - 1, j - 1]
            else if L[i - 1, j] = L[i, j] dann
                D[i, j] = D[i, j] + D[i - 1, j]
            endif
            if L[i, j - 1] = L[i, j] dann
                D[i, j] = D[i, j] + D[i, j - 1]
            endif
            if L[i - 1, j - 1] = L[i, j] dann
                D[i, j] = D[i, j] * D[i - 1, j - 1]
            endif
        end if
    endfor
endfor

Deine Antwort ist D[n, m]. Hoffentlich haben meine Ideen geholfen!

0voto

seviyor Punkte 96

Vielleicht kann Ihnen die Verwendung einer Art Trie helfen, die tatsächlichen Sequenzen zu generieren, während Sie die Länge berechnen, und danach die Gesamtanzahl mit einem Durchlauf im Trie berechnen (lineare Zeit).

Jetzt repräsentiert memo[i][j] die Länge der gemeinsamen Teilsequenz von A[i...m] und B[j..n]. Ich schlage vor, dass Sie auch ein lp[i][j] haben, das eine Liste von Zeigern darstellt, die jeweils auf einen Knoten im Trie zeigen, sodass der Pfad von diesem Knoten zur Wurzel des Tries Ihnen eine der längsten gemeinsamen Teilsequenzen für A[i...m] und B[j..n] gibt. Um dieses lp zu erstellen, kopieren Sie einfach die Listen aus lp[i+1][j] oder lp[i][j+1] für die Fälle 1 und 2, beide für Fall 4, während Sie für 3 einen neuen Knoten im Baum mit dem Wert A[i]=B[j] hinzufügen und im Baum alle von lp[i+1][j+1] zeigten Knoten als Söhne des neuen Knotens setzen. Diese Operationen wären linear (oder vielleicht sogar schneller mit einigen fortgeschrittenen Datenstrukturen zum Umgang mit Mengen). Beachten Sie, dass das, was ich beschrieben habe, eigentlich kein Trie/Baum ist (ein Knoten kann mehrere Eltern haben).

Zum Schluss glaube ich, dass für das Zählen eine Traversierung gut wäre, mit einigen zusätzlichen Verarbeitungsschritten - die Zählungen propagieren: count[Knoten][Ebene] = Summe(count[Söhne(Knoten)][Ebene-1]) oder, falls der Knoten ein Blatt ist, count[Knoten][1]=1, count[Knoten][l!=1]=0. Und Ihre Antwort wäre das Summieren der Zählungen für jene "Wurzel"-Knoten (Eingangsgrad 0), die die "längste" Bedingung erfüllen (\sum_x{count[x][l_max]}).

Ich bin mir nicht zu 100% sicher, ob meine Lösung richtig ist, aber es könnte ein guter Start für eine verbesserte Antwort auf Ihr Problem sein.

CodeJaeger.com

CodeJaeger ist eine Gemeinschaft für Programmierer, die täglich Hilfe erhalten..
Wir haben viele Inhalte, und Sie können auch Ihre eigenen Fragen stellen oder die Fragen anderer Leute lösen.

Powered by:

X