已知单词顺序与重叠关系,如何构建最短超串问题的结果?
解决LeetCode最短超串问题:已知顺序与重叠关系后的结果构造
问题背景
我花了3天时间啃LeetCode的Shortest Superstring(最短超串)问题,现在已经能确定单词的使用顺序和单词间的重叠关系,但就是拼不出最终的结果字符串。看了好多解决方案也没搞明白核心构造逻辑。
我的代码如下:
#include <iostream> #include <vector> using namespace std; struct Node { int cost; int next_mask; int next_node; Node(int cost, int mask, int next_node) : cost(cost), next_mask(mask), next_node(next_node) { } Node() : cost(-1), next_mask(-1), next_node(-1) { } bool is_done() const { return cost != -1; } }; // Cost = how many extra characters do I need to fully overlap w2. The characters from w1 are not extra pair<int, string> get_cost(string const& w1, string const& w2) { for (int i = 0; i < w1.size(); ++i) { string sub = w1.substr(i); int overlaps_start = w2.find(sub); if (overlaps_start != 0) continue; int overlapping_count = overlaps_start + sub.size(); return { w2.size() - overlapping_count, sub }; } return { w2.size(), "" }; } void solve(vector<vector<int>> const& graph, int current, int mask, vector<vector<Node>>& dp) { if (mask == (1 << graph.size()) - 1 || dp[mask][current].is_done()) return; for (int node = 0; node < graph.size(); ++node) { int check_state_mask = 1 << node; if (mask & check_state_mask) continue; int node_mask = mask | check_state_mask; solve(graph, node, node_mask, dp); int sub = graph[current][node] + dp[node_mask][node].cost; if (!dp[mask][current].is_done() || sub < dp[mask][current].cost) { dp[mask][current].cost = sub; dp[mask][current].next_mask = node_mask; dp[mask][current].next_node = node; } } } string shortestSuperstring(vector<string>& words) { vector<vector<int>> graph(words.size(), vector<int>(words.size())); vector<vector<string>> common_parts(words.size(), vector<string>(words.size())); for (int i = 0; i < words.size(); ++i) { for (int j = 0; j < words.size(); ++j) { pair<int, string> cost1 = get_cost(words[i], words[j]); pair<int, string> cost2 = get_cost(words[j], words[i]); graph[i][j] = cost1.first; graph[j][i] = cost2.first; common_parts[i][j] = cost1.second; common_parts[j][i] = cost2.second; } } // dp also tracks the path vector<vector<Node>> dp(1 << graph.size(), vector<Node>(graph.size(), Node())); solve(graph, 0, 1, dp); // We start from node 0 with mask = 1, so the result is at dp[1][0] Node parent = dp[1][0]; int parent_node = 0; while (parent.next_node != -1) { // What should happen here to build a result??? cout << parent_node << " : " << parent.next_node << " Common: " << common_parts[parent_node][parent.next_node] << "\n"; parent_node = parent.next_node; parent = dp[parent.next_mask][parent.next_node]; } return "im-too-weak-to-solve-it"; } int main() { vector<string> v{ "catg","ctaagt","gcta","ttca","atgcatc" }; std::cout << shortestSuperstring(v); }
我的核心困惑:运行代码后发现部分节点之间没有公共字符串,这时候不知道怎么插入对应的单词——不能跳过它,因为后续单词依赖它;也不确定把它放首尾会不会破坏最优性,更不知道怎么在已有串里找最优位置插入。现在想问:已知单词的使用顺序和两两重叠关系时,到底该怎么构造出最终的最短超串?
解决方案
核心构造逻辑
不管两个单词有没有重叠,构造逻辑都是统一的:
- 从起始单词开始,按照DP记录的路径顺序依次处理每个后续单词
- 对于当前已构造的字符串
current_str和下一个单词next_word,根据预先计算的common_parts[current_idx][next_idx](即current单词末尾和next单词开头的最长重叠子串),直接把next单词中不重叠的部分追加到current_str后面即可 - 如果没有重叠(common_parts为空串),就直接把整个next单词追加到后面——因为DP已经帮你选好了最优顺序,这时候直接追加就是当前最优的拼接方式,不需要额外调整位置
修改后的构造代码(重点部分)
将shortestSuperstring函数中构造结果的部分替换为以下代码:
string shortestSuperstring(vector<string>& words) { // ... 保留前面的graph和common_parts计算代码不变 ... // dp also tracks the path vector<vector<Node>> dp(1 << graph.size(), vector<Node>(graph.size(), Node())); solve(graph, 0, 1, dp); // 构造结果字符串 string result = words[0]; // 从起始单词开始 Node parent = dp[1][0]; int parent_node = 0; while (parent.next_node != -1) { int next_node = parent.next_node; string common = common_parts[parent_node][next_node]; // 截取next单词中不重叠的部分,追加到结果 result += words[next_node].substr(common.size()); parent_node = next_node; parent = dp[parent.next_mask][next_node]; } return result; }
关键说明
- DP路径的可靠性:你的DP逻辑已经计算出了总长度最短的单词拼接顺序,所以不需要再调整单词位置——哪怕两个单词没有重叠,直接追加就是最优选择(因为DP计算cost时已经考虑了无重叠的情况,此时cost等于next单词的长度,相当于直接追加)
- 重叠子串的作用:
get_cost函数已经正确找到了两个单词间最长的前缀重叠(w1的后缀匹配w2的前缀),所以common_parts中存储的是能最大化重叠长度的子串,用它截取不重叠部分能保证总长度最短 - 测试验证:对于测试用例
{"catg","ctaagt","gcta","ttca","atgcatc"},修改后的代码会输出最短超串(比如"gctaagttcatgcatc",具体结果可能因路径选择略有不同,但长度一致)
内容的提问来源于stack exchange,提问作者Szyszka947
相关产品推荐
相关产品推荐

