打孔/将多个字符串组合成一个(尽可能短的)字符串,其中包含前向方向上每个字符串的所有字符

时间:2017-07-23 19:54:24

标签: javascript node.js algorithm

我的目的是将多个字符串打入单个(最短)字符​​串,该字符串将包含正向中每个字符串的所有字符。问题不是针对任何语言,而是针对algorithm部分。 (可能会在节点服务器中实现它,因此标记nodejs/javascript)。

所以,解释一下这个问题:

让我们考虑一下我的字符串

["jack", "apple", "maven", "hold", "solid", "mark", "moon", "poor", "spark", "live"]

Resultant字符串应该类似于:

"sjmachppoalidveonrk"

jack:s j m ac hppoalidveonr k

apple:sjm a ch pp oa l idv e onrk

固体: s jmachpp o veonrk

====================================>>>>一切都在前进

这些都是手动评估,示例中输出可能不是100%完美。

所以,重点是每个字符串的所有字母都必须存在于输出中 FORWARD DIRECTION (这里实际问题属于),并且可能服务器将发送最终字符串,并生成27594之类的数字,并在必需的末尾传递令牌。如果我必须在尽可能小的字符串中打它,那将更容易(这种情况下只有唯一的字符就足够了)。但在这种情况下有一些观点:

  1. 信件可以多次出现,但我必须重复使用 如果可能的话,例如:for solidhold o > l > d可以 重新用作前向,但适用于applea > p)和sparkp > a)我们必须重复a,因为它出现在p之前的一个案例中 对于applep用于sparks,因此我们需要重复 ap。甚至,我们不能p > a > p,因为它不会涵盖这两种情况 因为在p

  2. a之后我们需要两个apple
  3. 我们没有选择放置单个p并使用相同的选项 在提取时间内索引两次,我们需要多个p而没有选项 左边的输入字符串包含

  4. 我(不)确定,一组可能有多个输出 字符串。但关注的是它应该是最小的, 如果它向前覆盖所有标记,则组合无关紧要。所有(或一个)最小可能长度的输出 需要追踪。
  5. 将此点作为编辑添加到此帖子中。阅读完评论并知道它已经存在 问题被称为shortest common supersequence problem我们可以 定义结果字符串将尽可能短 我们可以简单地从中生成任何输入字符串的字符串 删除一些(0到N)字符,这与在结果字符串中可以在正向找到的所有输入相同。
  6. 我试过,从一个任意的字符串开始,然后对下一个字符串进行分析并拆分所有字母,并相应地放置它们,但经过一段时间后,似乎当前的字符串字母可以放在一个更好的如果根据当前字符串放置了最后一个字符串(或前一个字符串)的字母。但是,再次对该字符串进行分析并根据处理的内容(多个)放置,并将某些内容放在有利于未处理的内容上似乎很困难,因为我们需要处理它。或者我可以维护所有已处理/未处理树的树,这将有助于构建最终字符串?还有比这更好的方法,它似乎是一种蛮力?

    注意:我知道还有很多其他转化可能,请尽量不建议使用其他任何内容,我们正在对此进行一些研究。

4 个答案:

答案 0 :(得分:7)

我想出了一种有点暴力的方法。这种方式可以找到组合2个单词的最佳方法,然后对数组中的每个元素进行组合。

这种策略的工作原理是尝试找到将两个单词组合在一起的最佳方法。拥有最少的字母被认为是最好的。每个单词都被输入一个不断增长的“合并”单词。每次添加新单词时,搜索现有单词以寻找存在于要合并的单词中的匹配字符。一旦发现一个被分成2组并试图加入(使用手头的规则,如果已存在字母,则不需要2添加等等)。该策略通常会产生良好的效果。

join_word方法需要加入2个单词,第一个参数被认为是您希望将另一个加入的单词。然后,它会搜索将intoword拆分为2个单独部分以便合并在一起的最佳方法,它通过查找任何共享的公共字符来完成此操作。这是splits_on_letter方法的用武之地。

splits_on_letter方法需要一个单词和一个你希望拆分的字母,然后返回一个二维数组,其中包含该字符分裂的所有可能的左右两侧。例如,splits_on_letter('boom', 'o')会返回[["b","oom"],["bo","om"],["boo","m"]],这就是我们如何使用字母o作为分割点的所有组合。

开头的sort()是尝试将相似的元素放在一起。合并元素的顺序通常会影响结果长度。我尝试过的一种方法是根据他们使用了多少常用字母(与他们的同伴)对它们进行排序,但结果却各不相同。然而,在我的所有测试中,我可能有5或6个不同的单词集进行测试,它可能会有更大,更多变化的单词数组,你可能会发现不同的结果。

输出

spmjhooarckpplivden


var words = ["jack", "apple", "maven", "hold", "solid", "mark", "moon", "poor", "spark", "live"];
var result = minify_words(words);
document.write(result);

function minify_words(words) {
    // Theres a good sorting method somewhere which can place this in an optimal order for combining them,
    // hoever after quite a few attempts i couldnt get better than just a regular sort... so just use that
    words = words.sort();

    /*
        Joins 2 words together ensuring each word has all its letters in the result left to right
    */
    function join_word(into, word) {
        var best = null;
        // straight brute force each word down. Try to run a split on each letter and 
        for(var i=0;i<word.length;i++) {
            var letter = word[i];
            // split our 2 words into 2 segments on that pivot letter
            var intoPartsArr = splits_on_letter(into, letter);
            var wordPartsArr = splits_on_letter(word, letter);
            for(var p1=0;p1<intoPartsArr.length;p1++) {
                for(var p2=0;p2<wordPartsArr.length;p2++) {
                    var intoParts = intoPartsArr[p1], wordParts = wordPartsArr[p2];
                    // merge left and right and push them together
                    var result = add_letters(intoParts[0], wordParts[0]) + add_letters(intoParts[1], wordParts[1]);
                    if(!best || result.length <= best.length) {
                        best = result;
                    }
                }
            }
        }

        // its possible that there is no best, just tack the words together at that point
        return best || (into + word);
    }

    /*
        Splits a word at the index of the provided letter
    */
    function splits_on_letter(word, letter) {
        var ix, result = [], offset = 0;;
        while((ix = word.indexOf(letter, offset)) !== -1) {
            result.push([word.substring(0, ix), word.substring(ix, word.length)]);
            offset = ix+1;
        }
        result.push([word.substring(0, offset), word.substring(offset, word.length)]);
        return result;
    }


    /*
        Adds letters to the word given our set of rules. Adds them starting left to right, will only add if the letter isnt found
    */
    function add_letters(word, addl) {
        var rIx = 0;
        for (var i = 0; i < addl.length; i++) {
            var foundIndex = word.indexOf(addl[i], rIx);
            if (foundIndex == -1) {
                word = word.substring(0, rIx) + addl[i] + word.substring(rIx, word.length);
                rIx += addl[i].length;
            } else {
                rIx = foundIndex + addl[i].length;
            }
        }
        return word;
    }

    // For each of our words, merge them together
    var joinedWords = words[0];
    for (var i = 1; i < words.length; i++) {
        joinedWords = join_word(joinedWords, words[i]);
    }
    return joinedWords;
}

答案 1 :(得分:3)

第一次尝试,没有真正优化(缩短183%):

function getShort(arr){
 var perfect="";
 //iterate the array
 arr.forEach(function(string){
   //iterate over the characters in the array
   string.split("").reduce(function(pos,char){    
     var n=perfect.indexOf(char,pos+1);//check if theres already a possible char
     if(n<0){      
       //if its not existing, simply add it behind the current

        perfect=perfect.substr(0,pos+1)+char+perfect.substr(pos+1);
       return pos+1;
     }
     return n;//continue with that char
    },-1);
  })
  return perfect;
}

In action

这可以通过简单地使用一些变体的数组运行上层代码(200%改进)来改进:

var s=["jack",...];
var perfect=null;
for(var i=0;i<s.length;i++){
 //shift
 s.push(s.shift());
 var result=getShort(s);
 if(!perfect || result.length<perfect.length) perfect=result;
}

In action

非常接近estimated字符的最小字符数(在最佳情况下最小化可能达到244%)

我还编写了一个函数来获取最少数量的字符,还有一个函数来检查某个单词是否失败,你可以找到它们here

答案 2 :(得分:3)

我已经使用了动态编程的概念,首先在正向中生成最短的字符串,如OP中所述。然后我将上一步中获得的结果与作为参数一起发送,并与列表中的下一个String一起发送。以下是java中的工作代码。希望这有助于达到最佳解决方案,以防我的解决方案被认为是非最佳的。请随时报告以下代码的任何反诉:

public String shortestPossibleString(String a, String b){
    int[][] dp = new int[a.length()+1][b.length()+1];
            //form the dynamic table consisting of 
            //length of shortest substring till that points 
    for(int i=0;i<=a.length();i++){
        for(int j=0;j<=b.length();j++){
            if(i == 0)
                dp[i][j] = j;
            else if(j == 0)
                dp[i][j] = i;
                            else if(a.charAt(i-1) == b.charAt(j-1))
                dp[i][j] = 1+dp[i-1][j-1];
            else
                dp[i][j] = 1+Math.min(dp[i-1][j],dp[i][j-1]);

        }
    }
            //Backtrack from here to find the shortest substring
            char[] sQ = new char[dp[a.length()][b.length()]];
            int s = dp[a.length()][b.length()]-1;
            int i=a.length(), j=b.length();
            while(i!=0 && j!=0){
                // If current character in a and b are same, then
                // current character is part of shortest supersequence
                if(a.charAt(i-1) == b.charAt(j-1)){
                    sQ[s] = a.charAt(i-1);
                    i--;
                    j--;
                    s--;
                }
                else {
                    // If current character in a and b are different
                    if(dp[i-1][j] > dp[i][j-1]){
                        sQ[s] = b.charAt(j-1);
                        j--;
                        s--;
                    }
                    else{
                        sQ[s] = a.charAt(i-1);
                        i--;
                        s--;
                    }
                }                        
            }
            // If b reaches its end, put remaining characters
            // of a in the result string
            while(i!=0){
                sQ[s] = a.charAt(i-1);
                i--;
                s--;
            }
            // If a reaches its end, put remaining characters
            // of b in the result string
            while(j!=0){
                sQ[s] = b.charAt(j-1);
                j--;
                s--;
            }
    return String.valueOf(sQ);
}
    public void getCombinedString(String... values){
        String sSQ = shortestPossibleString(values[0],values[1]);
        for(int i=2;i<values.length;i++){
            sSQ = shortestPossibleString(values[i],sSQ);
        }
        System.out.println(sSQ);
    }

驱动程序:

e.getCombinedString("jack", "apple", "maven", "hold", 
            "solid", "mark", "moon", "poor", "spark", "live");

输出:

jmapphsolivecparkonidr

当所有字符串都包含所有字符时,上述解决方案的最坏情况时间复杂度为O(product of length of all input strings),即使任何字符串对之间都没有匹配。

答案 3 :(得分:3)

这是一个基于JavaScript动态编程的最佳解决方案,但它只能在内存不足之前通过我的计算机上的solid。它与@ CodeHunter的解决方案的不同之处在于,它在每个添加的字符串之后保留整套最佳解决方案,而不仅仅是其中一个。您可以看到最佳解决方案的数量呈指数增长;即使在solid之后,已经有518,640个最佳解决方案。

const STRINGS = ["jack", "apple", "maven", "hold", "solid", "mark", "moon", "poor", "spark", "live"]
function map(set, f) {
    const result = new Set
    for (const o of set) result.add(f(o))
    return result
}
function addAll(set, other) {
    for (const o of other) set.add(o)
    return set
}
function shortest(set) { //set is assumed non-empty
    let minLength
    let minMatching
    for (const s of set) {
        if (!minLength || s.length < minLength) {
            minLength = s.length
            minMatching = new Set([s])
        }
        else if (s.length === minLength) minMatching.add(s)
    }
    return minMatching
}
class ZipCache {
    constructor() {
        this.cache = new Map
    }
    get(str1, str2) {
        const cached1 = this.cache.get(str1)
        if (!cached1) return undefined
        return cached1.get(str2)
    }
    set(str1, str2, zipped) {
        let cached1 = this.cache.get(str1)
        if (!cached1) {
            cached1 = new Map
            this.cache.set(str1, cached1)
        }
        cached1.set(str2, zipped)
    }
}
const zipCache = new ZipCache
function zip(str1, str2) {
    const cached = zipCache.get(str1, str2)
    if (cached) return cached

    if (!str1) { //str1 is empty, so only choice is str2
        const result = new Set([str2])
        zipCache.set(str1, str2, result)
        return result
    }
    if (!str2) { //str2 is empty, so only choice is str1
        const result = new Set([str1])
        zipCache.set(str1, str2, result)
        return result
    }
    //Both strings start with same letter
    //so optimal solution must start with this letter
    if (str1[0] === str2[0]) {
        const zipped = zip(str1.substring(1), str2.substring(1))
        const result = map(zipped, s => str1[0] + s)
        zipCache.set(str1, str2, result)
        return result
    }

    //Either do str1[0] + zip(str1[1:], str2)
    //or        str2[0] + zip(str1, str2[1:])
    const zip1 = zip(str1.substring(1), str2)
    const zip2 = zip(str1, str2.substring(1))
    const test1 = map(zip1, s => str1[0] + s)
    const test2 = map(zip2, s => str2[0] + s)
    const result = shortest(addAll(test1, test2))
    zipCache.set(str1, str2, result)
    return result
}
let cumulative = new Set([''])
for (const string of STRINGS) {
    console.log(string)
    const newCumulative = new Set
    for (const test of cumulative) {
        addAll(newCumulative, zip(test, string))
    }
    cumulative = shortest(newCumulative)
    console.log(cumulative.size)
}
console.log(cumulative) //never reached