我的带记忆的动态编程算法有什么问题?

What's wrong with my dynamic programming algorithm with memoization?

*抱歉我的英语不好。如果有什么不明白的,请告诉我,这样我可以给你更多的信息'make sence'。

**这是第一次在 Whosebug 提问。我在这里搜索了一些正确提问的规则,但应该有一些我遗漏的东西。我欢迎所有反馈。

我目前正在解决算法题来提高自己的技能,我在一个问题上挣扎了三天。这个问题来自 https://algospot.com/judge/problem/read/RESTORE ,但是因为这个页面是韩文的,所以我试着把它翻译成英文。

问题
如果给定 'k' 条部分字符串,计算包含所有部分字符串的最短字符串。 所有字符串仅包含小写字母。 如果满足所有条件且长度相同的结果字符串超过1个,则选择任意字符串。

输入
在输入的第一行,给出了测试用例的数量'C'(C<=50)。 对于每个测试用例,第一行给出部分字符串的数量 'k'(1<=k<=15),接下来的 k 行给出部分字符串。 部分字符串的长度在 1 到 40 之间。

输出
对于每个测试用例,打印包含所有部分字符串的最短字符串。

示例输入
3
3
地理
王子

2
世界
你好
3
胡言乱语
卡达布拉
dabr

示例输出
地理
你好世界
卡达布拉克

这是我的代码。我的代码似乎与示例输入完美配合,当我为自己进行测试输入并进行测试时,一切正常。但是当我提交这个代码时,他们说我的代码是'wrong'。

请告诉我我的代码有什么问题。您不需要告诉我整个固定代码,我只需要导致我的代码出错的示例输入。添加了代码说明,使我的代码更容易理解。

代码说明

将所有输入的部分字符串保存在向量“stringParts”中。
将当前最短字符串结果保存在全局变量“answer”中。
使用 'cache' 数组进行记忆 - 跳过重复的函数调用。

我设计的解决这个问题的算法分为两个函数—— restore() 和 eraseOverlapped().

restore() 函数计算包含 'stringParts'.
中所有部分字符串的最短字符串 resotre() 的结果保存在“answer”中。

对于 restore(),有三个参数 - 'curString'、'selected' 和 'last '.
'curString'代表当前选中的重叠字符串结果
'selected'代表'stringParts'当前选中的元素。使用位掩码使我的算法简洁。
'last'代表'stringParts'最后选择的元素,用于制作'curString'.

eraseOverlapped() 函数进行预处理 - 它会删除 'stringParts' 中可以在执行 restore() 之前完全包含到其他元素中的元素。

#include <algorithm>
#include <iostream>
#include <vector>
#include <cstring>
#include <string>
#define MAX 15
using namespace std;

int k;
string answer; // save shortest result string

vector<string> stringParts;
bool cache[MAX + 1][(1 << MAX) + 1]; //[last selected string][set of selected strings in Bitmask]

void restore(string curString, int selected=0, int last=0) {
    //base case 1
    if (selected == (1 << k) - 1) {
        if (answer.empty() || curString.length() < answer.length()) 
            answer = curString;
        return;
    }
    //base case 2 - memoization
    bool& ret = cache[last][selected];
    if (ret != false) return;

    for (int next = 0; next < k; next++) {
        string checkStr = stringParts[next];
        if (selected & (1 << next)) continue;

        if (curString.empty())
            restore(checkStr, selected + (1 << next), next + 1);
        else {
            int check = false;
            //count max overlapping area of two strings and overlap two strings.
            for (int i = (checkStr.length() > curString.length() ? curString.length() : checkStr.length())
                ; i > 0; i--) {
                if (curString.substr(curString.size()-i, i) == checkStr.substr(0, i)) {
                    restore(curString + checkStr.substr(i, checkStr.length()-i), selected + (1 << next), next + 1);
                    check = true;
                    break;
                }
            }
            if (!check) { // if there aren't any overlapping area
                restore(curString + checkStr, selected + (1 << next), next + 1);
            }
        }
    }
    ret = true;
}
//check if there are strings that can be completely included by other strings, and delete that string.
void eraseOverlapped() {
    //arranging string vector in ascending order of string length
    int vectorLen = stringParts.size();
    for (int i = 0; i < vectorLen - 1; i++) {
        for (int j = i + 1; j < vectorLen; j++) {
            if (stringParts[i].length() < stringParts[j].length()) {
                string temp = stringParts[i];
                stringParts[i] = stringParts[j];
                stringParts[j] = temp;
            }
        }
    }

    //deleting included strings
    vector<string>::iterator iter;
    for (int i = 0; i < vectorLen-1; i++) {
        for (int j = i + 1; j < vectorLen; j++) {
            if (stringParts[i].find(stringParts[j]) != string::npos) {
                iter = stringParts.begin() + j;
                stringParts.erase(iter);
                j--;
                vectorLen--;
            }
        }
    }
}

int main(void) {
    int C;
    cin >> C; // testcase
    for (int testCase = 0; testCase < C; testCase++) {
        cin >> k; // number of partial strings
        memset(cache, false, sizeof(cache)); // initializing cache to false
        string inputStr;
        for (int i = 0; i < k; i++) {
            cin >> inputStr;
            stringParts.push_back(inputStr);
        }
        eraseOverlapped();
        k = stringParts.size();

        restore("");
        cout << answer << endl;
        answer.clear();
        stringParts.clear();
    }
}

在确定哪些 string-parts 可以从列表中删除后,因为它们包含在其他 string-parts 中,对此问题建模的一种方法可能是“taxicab ripoff problem”问题(或 Max TSP),其中每个可能因重叠而减少的长度都被赋予正权重。考虑到问题中的输入量非常小,他们似乎期望接近 brute-force 的解决方案,可能带有一些启发式和回溯或其他形式的记忆。

感谢所有试图帮助我解决这个问题的人。我实际上解决了这个问题,对我以前的算法做了一些改动。这些是主要变化。

  1. 在我之前的算法中,我将 restore() 的结果保存在全局变量 'answer' 中,因为 restore() 没有 return 任何东西,但是在自 restore() returns mid-process 答案字符串以来的新算法我不再需要使用 'answer'.
  2. 使用字符串类型缓存而不是布尔类型缓存。我发现在这个算法中使用 bool 缓存进行记忆是没有用的。
  3. 从 restore() 中删除了 'curString' 参数。由于我们在递归调用时只需要一个之前选择的部分字符串,所以'last'可以代替'curString'的作用。

代码

#include <algorithm>
#include <iostream>
#include <vector>
#include <cstring>
#include <string>
#define MAX 15
using namespace std;

int k;

vector<string> stringParts;
string cache[MAX + 1][(1 << MAX) + 1];

string restore(int selected = 0, int last = -1) {
    if (selected == (1 << k) - 1) {
        return stringParts[last];
    }

    if (last == -1) {
        string ret = "";
        for (int next = 0; next < k; next++) {
            string resultStr = restore(selected + (1 << next), next);
            if (ret.empty() || ret.length() > resultStr.length())
                ret = resultStr;
        }
        return ret;
    }

    string& ret = cache[last][selected];
    if (!ret.empty()) {
        cout << "cache used in [" << last << "][" << selected << "]" << endl;
        return ret;
    }

    string curString = stringParts[last];
    for (int next = 0; next < k; next++) {
        if (selected & (1 << next)) continue;

        string checkStr = restore(selected + (1 << next), next);
        int check = false;
        string resultStr;
        for (int i = (checkStr.length() > curString.length() ? curString.length() : checkStr.length())
            ; i > 0; i--) {
            if (curString.substr(curString.size() - i, i) == checkStr.substr(0, i)) {
                resultStr = curString + checkStr.substr(i, checkStr.length() - i);
                check = true;
                break;
            }
        }
        if (!check)
            resultStr = curString + checkStr;

        if (ret.empty() || ret.length() > resultStr.length())
            ret = resultStr;
    }
    return ret;
}

void EraseOverlapped() {
    int vectorLen = stringParts.size();
    for (int i = 0; i < vectorLen - 1; i++) {
        for (int j = i + 1; j < vectorLen; j++) {
            if (stringParts[i].length() < stringParts[j].length()) {
                string temp = stringParts[i];
                stringParts[i] = stringParts[j];
                stringParts[j] = temp;
            }
        }
    }

    vector<string>::iterator iter;
    for (int i = 0; i < vectorLen - 1; i++) {
        for (int j = i + 1; j < vectorLen; j++) {
            if (stringParts[i].find(stringParts[j]) != string::npos) {
                iter = stringParts.begin() + j;
                stringParts.erase(iter);
                j--;
                vectorLen--;
            }
        }
    }
}

int main(void) {
    int C;
    cin >> C;
    for (int testCase = 0; testCase < C; testCase++) {
        cin >> k;
        for (int i = 0; i < MAX + 1; i++) {
            for (int j = 0; j < (1 << MAX) + 1; j++)
                cache[i][j] = "";
        }
        string inputStr;
        for (int i = 0; i < k; i++) {
            cin >> inputStr;
            stringParts.push_back(inputStr);
        }
        EraseOverlapped();
        k = stringParts.size();
        string resultStr = restore();
        cout << resultStr << endl;
        stringParts.clear();
    }
}

这个算法比我正在学习的书上建议的 'ideal' 算法慢很多,但它足够快,可以通过这道题的时间限制。