You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

已知单词顺序与重叠关系,如何构建最短超串问题的结果?

解决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;
}

关键说明

  1. DP路径的可靠性:你的DP逻辑已经计算出了总长度最短的单词拼接顺序,所以不需要再调整单词位置——哪怕两个单词没有重叠,直接追加就是最优选择(因为DP计算cost时已经考虑了无重叠的情况,此时cost等于next单词的长度,相当于直接追加)
  2. 重叠子串的作用:get_cost函数已经正确找到了两个单词间最长的前缀重叠(w1的后缀匹配w2的前缀),所以common_parts中存储的是能最大化重叠长度的子串,用它截取不重叠部分能保证总长度最短
  3. 测试验证:对于测试用例{"catg","ctaagt","gcta","ttca","atgcatc"},修改后的代码会输出最短超串(比如"gctaagttcatgcatc",具体结果可能因路径选择略有不同,但长度一致)

内容的提问来源于stack exchange,提问作者Szyszka947

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.06 05:25:57