将连续单词分组的算法,最小化每组的长度

时间:2017-10-20 08:24:10

标签: objective-c string algorithm pseudocode

从空格分隔的单词的输入,如何连接连续的单词,以便:

  • 每个组的最小长度为L(空格不计)
  • 最长的团长是最小的(空间不算数)

示例输入:

  一只猫会吃一只老鼠

最小长度示例:

L = 5

解决第一个条件而不是第二个条件的朴素算法:

  

当组的长度小于L时,将下一个单词连接到组
  如果最后一个组短于L,则将最后两个组连接在一起

这种天真的算法产生:

  • 第1组:
  • 第2组:acateat
  • 第3组:amouse
  • 最长组长度:7

第二个条件没有解决,因为更好的解决方案是:

  • 第1组:willa
  • 第2组:cateat
  • 第3组:amouse
  • 最长组长度:6

哪种算法可以解决作为程序执行速度相对较快的第二个条件(最小最长组)?(快速,我希望避免测试所有可能的组合)

我知道C,ObjC,Swift,Javascript,Python,但伪代码很好。

1 个答案:

答案 0 :(得分:3)

这可以通过动态编程方法完成。让我们计算一个函数F(i) - 在第一个i个单词的正确分割中成为最长组的最小长度。

F(0) = 0
F(i) = Min(Max(F(j), totalLen(j+1, i))), for j in [0..i-1] 

哪里

totalLen(i, j) = total length of words from i to j, if the length is at least L 
totalLen(i, j) = MAX, if total length is less than L 

答案是F(n)的价值。要自己获取群组,我们可以为每个j保存最佳i的索引。

c ++中有从头开始的实现:

const vector<string> words = {"would", "a", "cat", "eat", "a", "mouse"};
const int L = 5;
int n = words.size();

vector<int> prefixLen = countPrefixLen(words);

vector<int> f(n+1);
vector<int> best(n+1, -1);
int maxL = prefixLen[n];
f[0] = 0;

for (int i = 1; i <= n; ++i) {
    f[i] = maxL; 
    for (int j = 0; j < i; ++j) {
        int totalLen = prefixLen[i] - prefixLen[j];
        if (totalLen >= L) {
            int maxLen = max(f[j], totalLen);
            if (f[i] > maxLen) {
                f[i] = maxLen;
                best[i] = j;
            }
        }
    } 
}

output(f[n], prev, words);  

预处理和输出细节:

vector<int> countPrefixLen(const vector<string>& words) {
    int n = words.size();
    vector<int> prefixLen(n+1);
    for (int i = 1; i <= n; ++i) {
        prefixLen[i] = prefixLen[i-1] + words[i-1].length();
    } 
    return prefixLen;
}

void output(int answer, const vector<int>& best, const vector<string>& words) {
    cout << answer << endl;

    int j = best.size()-1;
    vector<int> restoreIndex(1, j);
    while (j > 0) {
        int i = best[j];
        restoreIndex.push_back(i);
        j = i;
    }
    reverse(restoreIndex.begin(), restoreIndex.end());
    for (int i = 0; i+1 < restoreIndex.size(); ++i) {
        for (int j = restoreIndex[i]; j < restoreIndex[i+1]; ++j) {
            cout << words[j] << ' ';
        }
        cout << endl;
    }
}

输出:

6
would a 
cat eat 
a mouse 

Runnable:https://ideone.com/AaV5C8

进一步改善

此算法的复杂性为O(N^2)。如果数据太慢,我可以建议一个简单的优化:

让内循环反转。首先,这允许摆脱prefixLen数组及其预处理,因为现在我们逐个向组添加单词(实际上,我们可以在初始版本中摆脱这种预处理,但是以简单为代价)。更重要的是,当totalLen不小于已经计算的f[i]时,我们可以打破循环,因为进一步的迭代永远不会导致改进。内循环的代码可以更改为:

int totalLen = 0;
for (int j = i-1; j >= 0; --j) {
    totalLen += words[j].length();
    if (totalLen >= L) {
        int maxLen = max(f[j], totalLen);
        if (f[i] > maxLen) {
            f[i] = maxLen;
            best[i] = j;
        }
    }
    if (totalLen >= f[i]) break;
} 

这可以极大地提高L的非常大的值的性能。