【问题标题】:Punch/Combine multiple strings into a single (shortest possible) string that includes all the chars of each strings in forward direction将多个字符串打孔/组合成一个(可能的最短)字符​​串,其中包括每个字符串的所有正向字符
【发布时间】:2018-07-16 18:07:34
【问题描述】:

我的目的是将多个字符串打成一个(最短的)字符串,该字符串将包含每个字符串的所有字符。这个问题不是特定于任何语言的,而是更多到algorithm 部分。 (可能会在节点服务器中实现,所以标记nodejs/javascript)。

所以,解释一下问题:

假设我的字符串很少

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

结果字符串应该是这样的:

"sjmachppoalidveonrk"

杰克:sjmachppoalidveonrk

apple: sjmachppoalidveonrk

solid:sjmachppoalidveonrk

=====================================>>>> 全部向前

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

所以,关键是每个字符串的所有字母都必须存在于输出中 FORWARD DIRECTION(这里是实际问题所在),并且可能服务器将发送最终的字符串和像27594 这样的数字将被生成并传递以在所需的末尾提取令牌。如果我必须用尽可能少的字符串打孔它会容易得多(这种情况只有唯一的字符就足够了)。但是在这种情况下有几点:

  1. 字母可以出现多次,但我必须重复使用任何字母 如果可能的话,请写信,例如:solidhold o > l > d 可以 重用为正向,但用于 apple (a > p) 和 sparkp > a)我们必须重复a,因为在一种情况下它出现在p之前 对于apple,在p 之后对于sparks 所以要么我们需要重复 ap。甚至,我们不能做p > a > p,因为它不会涵盖这两种情况 因为我们需要在a 之后为apple 提供两个p

  2. 我们直接没有选择放置单个p 并使用相同的 一次提取索引两次,我们需要多个p,没有选项 留下作为输入字符串包含该

  3. 我(不)确定,一组可能有多个输出 字符串。但令人担忧的是它的长度应该是最小的, 如果组合向前覆盖所有标记,则该组合无关紧要。最小可能长度的所有(或一个)输出 需要追踪。
  4. 将此点作为EDIT添加到此帖子中。在阅读了 cmets 并知道它已经存在之后 问题被称为shortest common supersequence problem 我们可以 定义结果字符串将是最短的 我们可以从中重新生成任何输入字符串的字符串 删除一些(0 到 N)个字符,这与所有输入都可以在结果字符串中的正向找到相同。

我尝试过,从一个任意字符串开始,然后分析下一​​个字符串并拆分所有字母,并相应地放置它们,但是经过一段时间后,似乎可以将当前字符串字母放在更好的位置方式,如果根据当前字符串放置最后一个字符串(或前一个字符串)的字母。但是再一次,该字符串是根据处理过的东西(多个)进行分析和放置的,并且将一些东西放在有利于未处理的东西上似乎很困难,因为我们需要处理它。或者我是否可以维护所有已处理/未处理树的树将有助于构建最终字符串?有什么比它更好的方法,它似乎是一个蛮力?

注意:我知道还有很多其他的转换可能,请尽量不要建议使用其他任何东西,我们正在研究它。

【问题讨论】:

  • @Downvoters,您可以在评论中解释原因,因为这对其他人也有帮助。
  • 我没有投票,但这看起来像是 CS 作业问题,可能是负面关注的原因。
  • @viraj_os 好吧,一点也不,至少如果有人这么认为可以澄清,或者如果不能有解决方案,至少不应该气馁。无论如何感谢您的评论。
  • 这个问题被称为shortest common supersequence problem
  • @mhumthanx 获取信息。这里的情况完全一样,我们可以很容易地找到 2 个字符串,但是超过 2 个字符串就很难找到解决方案(最好的情况)

标签: javascript node.js algorithm


【解决方案1】:

我想出了一个有点蛮力的方法。这种方式会找到组合 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;
}

【讨论】:

  • 嗯,看起来蛮暴力的。无论如何,感谢您的努力和参与。你能解释一下高层次的流程吗?无论如何,真的需要那种吗??
  • @KoushikChatterjee 更新了我的答案,不需要排序,但它通常似乎减小了大小。在我放入的更新中也有更多细节。在仔细研究和解决问题后,我认为如果您正在寻找最佳结果,总会有某种蛮力进行。
  • hmmm.. 所以,当您处理每个单词时,您似乎在尝试更新现有输出(迄今为止的组合字符串字符串),然后再将当前字符串的字符(如果需要)放到以这样的方式输出,以便我们在当前输出中需要最少的条目。这很好,不知何故,我也觉得(从一开始我就认为)这是在最佳处理中实现最短结果的关键。好的。 +1
  • 对于给定的示例,返回的解决方案是最佳的(对于所有字符,找到出现次数最多的单词并总结...但是至少存在一个冲突 spark/apple,a 出现在之前一次并且在 p 之后一次,因此最优解必须至少长一个字符...这正是返回解的字符数,因此它必须是最优的)。
【解决方案2】:

第一次尝试,没有真正优化(缩短 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

这非常接近 ive estimated 的最小字符数(在最好的情况下可能会最小化 244%)

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

【讨论】:

  • 第 1 部分:感谢您的回复,有点像我尝试过的事情(处理每个项目并将它们的字符放在输出中,正如我在问题中提到的那样),现在在优化中它将改变像[S1, S2, S3][S2, S3, S1][S3, S1, S2] 这样的inout 序列,并将采取最好(最短)的顺序
  • 第 2 部分:我觉得(正如您也提到的)如果我们可以采用所有组合(例如包括 [S1, S3, S2][S2, S1, S3] 和 ....)那么可能会得到最短的,那就是又是一个完整的蛮力。但是对于放置字符几乎是好的,我会说。无论如何,感谢您对解决此问题的回应和参与。 :)
  • @KoushikChatterjee 我仍在研究更好的方法...尚未找到……/
【解决方案3】:

我使用了动态编程的思想,首先在正向生成最短的字符串,如OP中所述。然后我将上一步中获得的结果与列表中的下一个字符串一起作为参数发送。以下是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)

【讨论】:

  • hjmsappoorcklivedn
  • @greybeard: hjmsappoorcklivedn 不正确。它不会向前形成spark。请检查。
  • 正确 - 然后制作 hjmsapapoorcklived。 (重新格式化/重新排序“jmhsa poopa rckli vedn”时出错……)
  • @greybeard:将检查该部分并更新。感谢您指出。
  • 挖多了,看来n&gt;2这个问题是NP-Complete
【解决方案4】:

这里有一个基于 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

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2013-12-25
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-02-02
    相关资源
    最近更新 更多