Verificação de Intercalação de Strings com Programação Dinâmica

Este problema envolve determinar se uma terceira string é formada pela intercalação de caracteres de duas outras strings, mantendo a ordem original dos caracteres dentro de cada uma das duas primeiras strings. Por exemplo, "cat" e "tree" podem formar "cattree", mas não "tretac".

Uma abordagem inicial para resolver este problema é a recursão. Podemos usar três ponteiros para rastrear a posição atual em cada uma das três strings, começando em 0. A função recursiva verifica se o caractere atual na terceira string corresponde ao caractere atual na primeira ou na segunda string. Se houver uma correspondência, avançamos o ponteiro correspondente e o ponteiro da terceira string e continuamos recursivamente.


#include <iostream>
#include <cstring>

char str1[201], str2[201], combinedStr[401];
int len1, len2, lenCombined;

bool canForm(int idx1, int idx2, int idxCombined) {
    // Caso base: se todos os caracteres da string combinada foram verificados
    if (idxCombined == lenCombined) {
        return true;
    }

    bool possible = false;

    // Tenta combinar com o caractere de str1
    if (idx1 < len1 && str1[idx1] == combinedStr[idxCombined]) {
        possible = possible || canForm(idx1 + 1, idx2, idxCombined + 1);
    }

    // Tenta combinar com o caractere de str2
    if (idx2 < len2 && str2[idx2] == combinedStr[idxCombined]) {
        possible = possible || canForm(idx1, idx2 + 1, idxCombined + 1);
    }

    return possible;
}

int main() {
    int numTestCases;
    std::cin >> numTestCases;
    for (int i = 1; i <= numTestCases; ++i) {
        std::cin >> str1 >> str2 >> combinedStr;
        len1 = strlen(str1);
        len2 = strlen(str2);
        lenCombined = strlen(combinedStr);

        // Verifica se o comprimento total das duas primeiras strings é igual ao da terceira
        if (len1 + len2 != lenCombined) {
            std::cout << "Data set " << i << ": no" << std::endl;
            continue;
        }

        if (canForm(0, 0, 0)) {
            std::cout << "Data set " << i << ": yes" << std::endl;
        } else {
            std::cout << "Data set " << i << ": no" << std::endl;
        }
    }
    return 0;
}

A abordagem recursiva, no entanto, pode levar a um Time Limit Exceeded (TLE) devido à computação de subproblemas repetidos. Para otimizar, podemos usar programação dinâmica. Identificamos a necessidade de:

  1. Definir uma relação de recorrência clara.
  2. Confirmar a existência de subproblemas sobrepostos.
  3. Resolver o problema de forma bottom-up.

Os subproblemas sobrepostos não são imediatamente óbvios, surgindo apenas quando os caracteres de str1 e str2 coincidem em certas posições. Por exemplo, em "cat" e "tree", não há sobreposição significativa. A relação de recorrência pode ser definida como dp\[i\]\[j\], que indica se os primeiros i caracteres de str1 e os primeiros j caracteres de str2 podem formar os primeiros i + j caratceres de combinedStr.

dp\[i\]\[j\] é determinado pela verdade de uma das seguintes condições (ou uma combinação delas):

  • dp\[i-1\]\[j\] e o i-ésimo caractere de str1 corrresponde ao (i+j)-ésimo caractere de combinedStr.
  • dp\[i\]\[j-1\] e o j-ésimo caractere de str2 corresponde ao (i+j)-ésimo caractere de combinedStr.

A implementação bottom-up utiliza uma tabela dp onde dp\[i\]\[j\] representa a possibilidade de formar combinedStr até o índice i+j-1 usando os primeiros i caracteres de str1 e os primeiros j caracteres de str2.


#include <iostream>
#include <vector>
#include <string>
#include <cstring>

char str1[201], str2[201], combinedStr[401];
bool dp[201][201];
int len1, len2, lenCombined;

int main() {
    std::ios_base::sync_with_stdio(false);
    std::cin.tie(NULL);

    int numTestCases;
    std::cin >> numTestCases;
    for (int k = 1; k <= numTestCases; ++k) {
        std::cin >> str1 >> str2 >> combinedStr;
        len1 = strlen(str1);
        len2 = strlen(str2);
        lenCombined = strlen(combinedStr);

        if (len1 + len2 != lenCombined) {
            std::cout << "Data set " << k << ": no" << std::endl;
            continue;
        }

        // Inicializa a tabela DP
        memset(dp, false, sizeof(dp));

        // Caso base: strings vazias formam uma string combinada vazia
        dp[0][0] = true;

        // Preenche a primeira coluna (considerando apenas str1)
        for (int i = 1; i <= len1; ++i) {
            if (dp[i - 1][0] && str1[i - 1] == combinedStr[i - 1]) {
                dp[i][0] = true;
            }
        }

        // Preenche a primeira linha (considerando apenas str2)
        for (int j = 1; j <= len2; ++j) {
            if (dp[0][j - 1] && str2[j - 1] == combinedStr[j - 1]) {
                dp[0][j] = true;
            }
        }

        // Preenche o restante da tabela DP
        for (int i = 1; i <= len1; ++i) {
            for (int j = 1; j <= len2; ++j) {
                // Verifica se o caractere atual de combinedStr pode vir de str1
                bool fromStr1 = dp[i - 1][j] && (str1[i - 1] == combinedStr[i + j - 1]);
                // Verifica se o caractere atual de combinedStr pode vir de str2
                bool fromStr2 = dp[i][j - 1] && (str2[j - 1] == combinedStr[i + j - 1]);

                dp[i][j] = fromStr1 || fromStr2;
            }
        }

        if (dp[len1][len2]) {
            std::cout << "Data set " << k << ": yes" << std::endl;
        } else {
            std::cout << "Data set " << k << ": no" << std::endl;
        }
    }
    return 0;
}

É importante notar que a condição dp\[i-1\]\[j-1\] mencionada na análise original não é estritamente necessária se considerarmos as transições a partir de dp\[i-1\]\[j\] e dp\[i\]\[j-1\] separadamente, pois elas já cobrem todos os casos.

Tags: programação dinâmica C++ Strings intercalação de strings Algoritmos

Publicado em 7-2 19:57