鉴于两个序列 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,即使它会导致重复
答案 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)]
中移除arr2
:arr1
。
另一方面,即使我们发现多项式运行时复杂性O(length(arr1) * length(arr2))
可以从arr2
中移除 - 仍然可能存在指数量的可能方法来移除{{1}来自arr1
。
例如,请考虑问题的实例:何时需要从arr2
中删除arr1
。有arr2 = [1,1,1]
个方法可以做到。
尽管如此,下面是自下而上的带有回溯的动态编程解决方案,对于许多给定问题的实例来说,它仍然具有比指数更好的运行时复杂性:
arr1 = [1,1,1,1,1,1,1]
7!/(3! * 4!) = 35
&#13;
答案 3 :(得分:3)
算法:
[1,2,1,3,1,4,4], [1,4,4]
树将是[ [ 0, [5, [6]], [6] ], [ 2, [5, [6]], [6] ], [ 4, [5, [6]], [6] ]
。[ [ 0, 5, 6 ], [ 2, 5, 6 ], [ 4, 5, 6 ] ]
。[ [ 2, 1, 3, 1 ], [ 1, 2, 3, 1 ], [ 1, 2, 1, 3 ] ]
。执行此操作的代码,它匹配您的所有测试用例:
#!/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'])));