(因为在问题的原始版本中不清楚您是要删除子序列还是无序列表,所以我更新了我的答案以解决这两种情况。)
1.按顺序删除一个子序列
我们得到一个值序列,例如[1,5,1,3,4,2,3,2,1,3,1,2]
,以及要从第一个序列中删除的值的子序列,例如[1,3,2,1]
。如果我们找到值的每个实例在序列中的位置,我们会得到如下图:

找到可以按顺序从序列中删除值的所有方式,然后意味着找到可以将底部行中的待删除值连接到序列中的实例的所有方式,而没有任何线交叉,例如:

为了避免以不会导致有效解决方案的方式删除值(例如,从删除最右边的值 1 开始,之后没有可以删除的值 3),我们将首先删除图中的所有连接导致这种无效的解决方案。
这将通过对子序列进行两次迭代来完成。首先,我们从左到右执行此操作,对于每个值,我们查看其最左侧的连接,并从其右侧的值中删除任何与该连接相交或交叉的连接;例如,当考虑值 2 的最左侧连接时,其右侧的值 1 的两个连接(以红色表示)被删除,因为它们穿过此连接:

在下一步中,我们从右到左,对于每个值,我们查看其最右侧的连接,并从其左侧的值中删除任何与该连接相遇或交叉的连接;例如,当考虑从右边的值 1 开始的最右边的连接时,从左边的值 2 开始的最右边的连接(用红色表示)被删除,因为它穿过了这个连接:

然后我们最终得到如下所示的简化图。然后通过组合子序列中每个值的每个连接实例来进行组合,使用例如递归:您迭代子序列中第一个值的选项,并且每次递归子序列的其余部分,以便第一个值的选项与其他值的所有选项相结合。

简化图中可以存在交叉连接,但这些不再是问题。在示例中,您将看到即使为值 3 选择了正确的连接,也有一个与值 2 不交叉的连接:

将其转换为代码相对简单;在代码片段下方,您将找到包含示例数据的代码运行。
function removeSubSeq(seq, sub) {
var posi = []; // list position of values in seq, then connect to positions in sub
sub.forEach(function(elem) {posi[elem] = []});
seq.forEach(function(elem, index) {if (posi[elem]) posi[elem].push(index)});
var conn = sub.map(function(elem) {return posi[elem].slice()});
for (var i = 1; i < conn.length; i++) {
var left = conn[i - 1][0];
while (conn[i][0] <= left) {
conn[i].shift(); // remove crossed connection left-to-right
}
}
for (var i = conn.length - 2; i >= 0; i--) {
var right = conn[i + 1][conn[i + 1].length - 1];
while (conn[i][conn[i].length - 1] >= right) {
conn[i].pop(); // remove crossed connection right-to-left
}
}
var combinations = [], result = [];
combine(0, -1, []); // initiate recursion to find combinations
for (var i = 0; i < combinations.length; i++) {
result[i] = seq.slice(); // create copies of seq and remove values
for (var j = combinations[i].length - 1; j >= 0; j--) {
result[i].splice(combinations[i][j], 1);
}
}
return result;
function combine(step, prev, comb) { // generate combinations using recursion
for (var i = 0; i < conn[step].length; i++) {
var curr = conn[step][i];
if (prev >= curr) continue; // skip crossed connection
if (step + 1 == conn.length) combinations.push(comb.concat([curr]));
else combine(step + 1, curr, comb.concat([curr]));
}
}
}
var result = removeSubSeq([1,5,1,3,4,2,3,2,1,3,1,2], [1,3,2,1]);
for (var i in result) document.write(result[i] + "<br>");
代码执行以下步骤:
- 创建 sub 中存在的每个值的实例位置的关联数组:
posi[1] = [0,2,8,10], posi[2] = [5,7,11], posi[3] = [3,6,9]}
- 创建一个二维数组,将子序列中的位置链接到序列中的位置:
conn = [[0,2,8,10],[3,6,9],[5,7,11],[0,2,8,10]]
conn = [[0,2,8,10],[3,6,9],[5,7,11],[8,10]]
conn = [[0,2],[3,6],[5,7],[8,10]]
combinations = [[0,3,5,8],[0,3,5,10],[0,3,7,8],[0,3,7,10],
[0,6,7,8],[0,6,7,10],[2,3,5,8],[2,3,5,10],
[2,3,7,8],[2,3,7,10],[2,6,7,8],[2,6,7,10]]
- 使用组合从序列副本中删除值(请参阅代码片段输出)。
2. 移除一个无序列的值列表
如果要删除的值列表不一定是主序列的子序列,并且可以以任意顺序删除值,则可以使用与上述相同的方法,但放宽交叉连接规则:
从位置列表中删除交叉连接,并在生成组合时避免交叉连接,只需要对相同的值进行。
在此示例中,仅删除了重复值 1 的交叉连接;第一个从左到右:

然后从右到左:

生成此简化图,删除了值 1 的有问题的交叉连接,并保留了值 2 和 3 的交叉连接:

以下是改编自子序列版本的代码示例;如注释所示,仅更改了代码中的几行(我还使用了不同的方法从序列中删除了值)。要删除的值列表在开始时进行排序,以便将相同的值组合在一起,以便于跟踪相同的值。
function removeSubList(seq, sub) {
sub.sort(function(a, b) {return a - b}); /* SORT SUB-LIST FIRST */
var posi = []; // list position of values in seq, then connect to positions in sub
sub.forEach(function(elem) {posi[elem] = []});
seq.forEach(function(elem, index) {if (posi[elem]) posi[elem].push(index)});
var conn = sub.map(function(elem) {return posi[elem].slice()});
for (var i = 1; i < conn.length; i++) {
if (sub[i - 1] != sub[i]) continue; /* SKIP FOR NON-IDENTICAL VALUES */
var left = conn[i - 1][0];
while (conn[i][0] <= left) {
conn[i].shift(); // remove crossed connection left-to-right
}
}
for (var i = conn.length - 2; i >= 0; i--) {
if (sub[i] != sub[i + 1]) continue; /* SKIP FOR NON-IDENTICAL VALUES */
var right = conn[i + 1][conn[i + 1].length - 1];
while (conn[i][conn[i].length - 1] >= right) {
conn[i].pop(); // remove crossed connection right-to-left
}
}
var combinations = [], result = [];
combine(0, -1, []); // initiate recursion to find combinations
for (var i = 0; i < combinations.length; i++) {
var s = seq.slice(); // create copy of seq and delete values
combinations[i].forEach(function(elem) {delete s[elem]});
result[i] = s.filter(function(elem) {return elem != undefined});
}
return result;
function combine(step, prev, comb) { // generate combinations using recursion
for (var i = 0; i < conn[step].length; i++) {
var curr = conn[step][i];
if (prev >= curr && seq[prev] == seq[curr]) continue; /* SKIP FOR NIV */
if (step + 1 == conn.length) combinations.push(comb.concat([curr]));
else combine(step + 1, curr, comb.concat([curr]));
}
}
}
var result = removeSubList([1,5,1,3,4,2,3,2,1,3,1,2], [1,3,1,2,1]);
for (var i in result) document.write(result[i] + "<br>");