如何确定从序列中删除子序列的所有可能方法?

时间:2016-08-21 03:19:13

标签: javascript algorithm sequence

鉴于两个序列 A B ,我如何生成 B 可以从< B 中移除的所有可能方式的列表EM> A 的

例如,在JavaScript中,如果我有一个函数removeSubSeq采用两个我想要的数组参数,它将按如下方式工作:

removeSubSeq([1,2,1,3,1,4,4], [1,4,4])会返回[ [2,1,3,1], [1,2,3,1], [1,2,1,3] ],因为最后的4s会匹配,并且有1个匹配的可能位置

removeSubSeq([8,6,4,4], [6,4,8])将返回[],因为第二个参数实际上不是子序列

removeSubSeq([1,1,2], [1])会返回[ [1,2], [1,2] ],因为有两种方法可以删除1,即使它会导致重复

6 个答案:

答案 0 :(得分:18)

此问题可以在O(n*m + r)时间内解决,其中r是结果的总长度,使用经典的longest common subsequence算法。

制作完表后,就像在Wikipedia的example中一样,将其替换为带有对角线箭头的单元格列表,该箭头也具有与其行对应的值。现在从最后一行中的对角线向后遍历每个单元格,在字符串中累积相关索引并复制和分割累积,使得每个具有斜箭头的单元格将具有前一行中具有对角线的所有单元格的延续,在它的左边(存储也计算,当你构建矩阵)和一个较少的值。当累积达到零单元时,拼接索引中的累积索引并将其作为结果添加。

(箭头对应的LCS目前是否来自LCS(X[i-1],Y[j]) and/or LCS(X[i],Y[j-1]), or LCS(X[i-1],Y[j-1]),请参阅函数definition。)

例如:

  0  a  g  b  a  b  c  c
0 0  0  0  0  0  0  0  0
a 0 ↖1  1  1 ↖1  1  1  1
b 0  1  1 ↖2  2 ↖2  2  2
c 0  1  1  2  2  2 ↖3 ↖3

JavaScript代码:

function remove(arr,sub){
  var _arr = [];
  arr.forEach(function(v,i){ if (!sub.has(i)) _arr.push(arr[i]); });
  return _arr;
}

function f(arr,sub){
  var res = [],
      lcs = new Array(sub.length + 1),
      nodes = new Array(sub.length + 1);
     
  for (var i=0; i<sub.length+1;i++){
    nodes[i] = [];
    lcs[i] = [];
   
    for (var j=0; j<(i==0?arr.length+1:1); j++){
      // store lcs and node count on the left
      lcs[i][j] = [0,0];
    }
  }
 
  for (var i=1; i<sub.length+1;i++){ 
    for (var j=1; j<arr.length+1; j++){
      if (sub[i-1] == arr[j-1]){
        lcs[i][j] = [1 + lcs[i-1][j-1][0],lcs[i][j-1][1]];
       
        if (lcs[i][j][0] == i){
                  // [arr index, left node count above]
          nodes[i].push([j - 1,lcs[i-1][j-1][1]]);
       
          lcs[i][j][1] += 1;
        }
       
      } else {
        lcs[i][j] = [Math.max(lcs[i-1][j][0],lcs[i][j-1][0]),lcs[i][j-1][1]];
      }
    }
  }
   
  function enumerate(node,i,accum){
    if (i == 0){
      res.push(remove(arr,new Set(accum)));
      return;
    }
    
    for (var j=0; j<node[1]; j++){
      var _accum = accum.slice();
      _accum.push(nodes[i][j][0]);
      
      enumerate(nodes[i][j],i - 1,_accum);
    }
  }
  
  nodes[sub.length].forEach(function(v,i){ 
    enumerate(nodes[sub.length][i],sub.length - 1,[nodes[sub.length][i][0]]); 
  });

  return res;
}

console.log(JSON.stringify(f([1,2,1,3,1,4,4], [1,4,4])));
console.log(JSON.stringify(f([8,6,4,4], [6,4,8])));
console.log(JSON.stringify(f([1,1,2], [1])));
console.log(JSON.stringify(f(['a','g','b','a','b','c','c'], ['a','b','c'])));

答案 1 :(得分:7)

您可以使用递归。通过遍历A并按顺序推送元素来构建新的子序列C.每当遇到与B的头部匹配的元素时,您将把递归分成两个路径:一个用于从A和B中删除(即跳过)元素,另一个用于忽略它并继续照常操作。

如果你耗尽了所有的B(意味着你&#34;删除&#34; B中的所有元素都来自A),那么将A的其余部分附加到C将产生有效的子序列。否则,如果你在没有耗尽所有B的情况下到达A的末尾,则C不是有效的子序列,应该被丢弃。

function removeSubSeq(a, b) {
    function* remove(i, j, c) {
        if (j >= b.length) {
            yield c.concat(a.slice(i));
        } else if (i >= a.length) {
            return;
        } else if (a[i] === b[j]) {
            yield* remove(i + 1, j + 1, c);
            yield* remove(i + 1, j, c.concat(a.slice(i, i + 1)));
        } else {
            yield* remove(i + 1, j, c.concat(a.slice(i, i + 1)));
        }
    }

    if (a.length < b.length) {
        return [];   
    }

    return Array.from(remove(0, 0, []));
}

通过使用简单的push()/ pop()对替换每个递归分支中Array.concat的使用,可以使内部辅助函数稍微提高效率,但这会使控制流更难以实现神交。

function* remove(i, j, c) {
    if (j >= b.length) {
        yield c.concat(a.slice(i));
    } else if (i >= a.length) {
        return;
    } else {
        if (a[i] === b[j]) {
            yield* remove(i + 1, j + 1, c);
        }

        c.push(a[i]);
        yield* remove(i + 1, j, c);
        c.pop();
    }
}

答案 2 :(得分:7)

使用自下而上的动态编程方法和回溯可以解决此问题。

让我们考虑一个递归关系f(i1, i2),这有助于检查序列arr2 tail 是否可以从尾部删除< / em>序列arr1

f(i1, i2) = true, if(i1 == length(arr1) AND i2 == length(arr2))
f(i1, i2) = f(i1 + 1, i2) OR f(i1 + 1, i2 + 1), if(arr1[i1] == arr2[i2])
f(i1, i2) = f(i1 + 1, i2), if(arr1[i1] != arr2[i2])

solution  = f(0, 0)

我使用术语 tail 来表示arr1的子序列,该子序列从索引i1开始并跨越到arr1的末尾(并且相同) arr2的{​​{1}} - arr2从索引i2开始,并跨越arr2的末尾。

让我们从给定的递归关系的自上而下的实现开始(但没有记忆,为了使解释简单)。下面是Java代码片段,在删除arr1后打印arr2的所有可能子序列:

void remove(int[] arr1, int[] arr2) {
    boolean canBeRemoved = remove(arr1, arr2, 0, 0, new Stack<>());
    System.out.println(canBeRemoved);
}

boolean remove(int[] arr1, int[] arr2, int i1, int i2, Stack<Integer> stack) {

    if (i1 == arr1.length) {
        if (i2 == arr2.length) {
            // print yet another version of arr1, after removal of arr2
            System.out.println(stack);
            return true;
        }
        return false;
    }

    boolean canBeRemoved = false;
    if ((i2 < arr2.length) && (arr1[i1] == arr2[i2])) {
        // current item can be removed
        canBeRemoved |= remove(arr1, arr2, i1 + 1, i2 + 1, stack);
    }

    stack.push(arr1[i1]);
    canBeRemoved |= remove(arr1, arr2, i1 + 1, i2, stack);
    stack.pop();

    return canBeRemoved;
}

提供的代码片段不使用任何记忆技术,对于给定问题的所有实例都具有指数运行时复杂性

但是,我们可以看到变量i1只能包含区间[0..length(arr1)]中的值,变量i2也只能包含区间{{1}中的值}。

因此,可以检查是否可以使用多项式运行时复杂度从[0..length(arr2)]中移除arr2arr1

另一方面,即使我们发现多项式运行时复杂性O(length(arr1) * length(arr2))可以从arr2中移除 - 仍然可能存在指数量的可能方法来移除{{1}来自arr1

例如,请考虑问题的实例:何时需要从arr2中删除arr1。有arr2 = [1,1,1]个方法可以做到。

尽管如此,下面是自下而上的带有回溯的动态编程解决方案,对于许多给定问题的实例来说,它仍然具有比指数更好的运行时复杂性:

arr1 = [1,1,1,1,1,1,1]

所描述解决方案的JavaScript实现

&#13;
&#13;
7!/(3! * 4!) = 35
&#13;
&#13;
&#13;

答案 3 :(得分:3)

算法:

  1. 从B中的第一个元素开始递归构建节点树。每个节点的值是与其级别匹配的子序列项的索引,其后代是下一个项的索引 - 因此对于[1,2,1,3,1,4,4], [1,4,4]树将是[ [ 0, [5, [6]], [6] ], [ 2, [5, [6]], [6] ], [ 4, [5, [6]], [6] ]
  2. 遍历此树并构建要删除的项的子序列,即查找树中与子序列一样长的所有路径。这会产生类似[ [ 0, 5, 6 ], [ 2, 5, 6 ], [ 4, 5, 6 ] ]
  3. 的列表
  4. 对于如此开发的每个列表,请附加要删除的索引处的元素的结果列表:[ [ 2, 1, 3, 1 ], [ 1, 2, 3, 1 ], [ 1, 2, 1, 3 ] ]
  5. 执行此操作的代码,它匹配您的所有测试用例:

    #!/usr/bin/env node
    
    var _findSubSeqs = function(outer, inner, current) {
    
        var results = [];
        for (var oi = current; oi < outer.length; oi++) {
            if (outer[oi] == inner[0]) {
                var node = {
                    value: oi,
                    children: _findSubSeqs(outer, inner.slice(1), oi+1)
                };
                results.push(node);
                }
        }
        return results;
    }
    
    var findSubSeqs = function(outer, inner) {
        var results = _findSubSeqs(outer, inner, 0);
        return walkTree(results).filter(function(a) {return (a.length == inner.length)});
    }
    
    var _walkTree = function(node) {
        var results = [];
        if (node.children.length) {
            for (var n = 0; n < node.children.length; n++) {
                var res = _walkTree(node.children[n])
                for (r of res) {
                    results.push([node.value].concat(r))
                }
            }
        } else {
            return [[node.value]]
        }
        return results
    }
    
    var walkTree = function(nds) {
        var results = [];
        for (var i = 0; i < nds.length; i++) {
            results = results.concat(_walkTree(nds[i]))
        }
        return results
    }
    
    var removeSubSeq = function(outer, inner) {
        var res = findSubSeqs(outer, inner);
        var subs = [];
        for (r of res) {
            var s = [];
            var k = 0;
            for (var i = 0; i < outer.length; i++) {
                if (i == r[k]) {
                    k++;
                } else {
                    s.push(outer[i]);
                }
            }
            subs.push(s);
        }
        return subs
    }
    
    console.log(removeSubSeq([1,2,1,3,1,4,4], [1,4,4]))
    console.log(removeSubSeq([8,6,4,4], [6,4,8]) )
    console.log(removeSubSeq([1,1,2], [1]))
    

答案 4 :(得分:2)

首先我会使用字符串。操作起来比较容易:

var results = [];

function permute(arr) {
    var cur, memo = [];

    for (var i = 0; i < arr.length; i++) {
        cur = arr.splice(i, 1);
        if (arr.length === 0) {
            results.push(memo.concat(cur));
        }
        permute(arr.slice(), memo.concat(cur));
        arr.splice(i, 0, cur[0]);
    }
    return results;
}

function removeSub(arr, sub) {
    strArray = arr.join(' ');
    if(strArray.includes(sub)){
        return strArray.replace(sub.join(' ')).split(' ');
    }
    return [];
}

function removeSubSeq(arr, sub) {
    return permute(removeSub(arr, sub));
}

我没有评论代码,但请不要犹豫要求澄清。它没有经过测试,但想法就在其中......

答案 5 :(得分:1)

我的目标是尽可能少地创建和调用函数。这似乎有效。绝对可以清理干净。可以玩的东西...

function removeSubSeq( seq, sub ) {

    var arr,
        sub_v,
        sub_i = 0,
        seq_i,
        sub_len = sub.length,
        sub_lenm1 = sub_len - 1,
        seq_len = seq.length,
        pos = {},
        pos_len = [],
        c_pos,
        map_i = [],
        len,
        r_pos,
        sols = [],
        sol;

    do {

        map_i[ sub_i ] = 0;
        sub_v = sub[ sub_i ];

        if( pos[ sub_v ] ) {

            pos_len[ sub_i ] = pos_len[ sub_i - 1 ];
            continue;
        
        }

        arr = pos[ sub_v ] = [];

        c_pos = 0;

        seq_i = seq_len;

        while( seq_i-- ) {

            if( seq[ seq_i ] === sub_v ) {
                
                arr[ c_pos++ ] = seq_i;

            }

        }

        pos_len[ sub_i ] = arr.length;

    } while( ++sub_i < sub_len );

    len = pos[ sub[ 0 ] ].length;

    while( map_i[ 0 ] < len ) {

        sub_i = 0;
        arr = [];

        do {

            r_pos = pos[ sub[ sub_i ] ][ map_i[ sub_i ] ];
            
            if( sub_i && r_pos <= arr[ sub_i - 1] ) break;
            
            arr.push( r_pos );
        
        } while( ++sub_i < sub_len );

        if( sub_i === sub_len ) {
            
            sol = seq.slice( 0 );

            while( sub_i-- ) sol.splice( arr[ sub_i ], 1 );

            sols.push( sol );
        
        }

        sub_i = sub_lenm1;

        while( ++map_i[ sub_i ] === pos_len[ sub_i ] ) {
            if( sub_i === 0 ) break;
            map_i[ sub_i-- ] = 0;
        }

    } while( map_i[ 0 ] < len );

    return sols;

}

console.log(JSON.stringify(removeSubSeq([1,2,1,3,1,4,4], [1,4,4])));
console.log(JSON.stringify(removeSubSeq([8,6,4,4], [6,4,8])));
console.log(JSON.stringify(removeSubSeq([1,1,2], [1])));
console.log(JSON.stringify(removeSubSeq(['a','g','b','a','b','c','c'], ['a','b','c'])));