这个问题相当于longest increasing subsequence问题。
您必须定义一个比较运算符less。 less(a, b) 将返回 true 当且仅当 a 在目标序列中位于 b 之前。现在使用这个比较运算符,计算源序列的最大递增子序列。您将不得不移动不属于该子序列的每个元素(否则子序列将不是最大的),并且您可以将其移动一次(将其移动到其目标位置)。
编辑:根据 amit 的要求,这是我对上述陈述的证明:
让我们表示目标序列B,让我们表示源序列A。让n = |A| 和k 为上述最长递增序列的长度。
- 让我们假设可以从
A 到达B,移动次数少于n - k。这意味着至少来自A 的n - k + 1 元素不会被移动。令 s1,s2,...sm 为未移动的元素集合。从假设我们知道m > k。现在由于这些元素没有移动,因此它们相对于彼此的相对位置不能改变。因此,目标序列B 中所有这些元素的相对位置与A 中的位置相同。因此,上面定义的运算符 less(si, sj) 对于任何i、j 都应该为真。但如果这是真的,那么 s1,s2,...sm 正在增加序列,并且m > k 这会导致与 k 是最长递增序列长度的假设相矛盾。
- 现在让我们展示一个算法,通过移动所有元素(除了属于最长递增序列一部分的元素)从
A 到达B。我们将按照它们在 B 中出现的顺序移动元素。我们不会移动属于最长递增序列的元素。如果当前元素是 B 中的第一个元素,我们只需将其移动到序列的开头。否则,我们将当前元素移动到 B 中前一个元素的位置之后。请注意,此元素可能是我们已移动的前一个元素,也可能是最长递增序列中的一个元素。请注意,在我们将要移动索引为i 的元素的每一步中,所有索引为1, 2, ...i-1 的元素都将具有正确的相对位置。
编辑:添加一些代码以使答案更清晰。我觉得自己不是 javascript 专家,因此请随时纠正或批评我的解决方案。
让我们定义一个函数transform(a, s),它接受两个参数——如语句中所述列出a 和b。首先,我将创建一个映射positions,将a 中的每个元素映射到其在s 中的位置:
var positions = {};
for (var i = 0; i < a.length; ++i) {
positions[a[i]] = i;
}
现在我有了这个数组,我可以在上面的答案中定义一个辅助函数。 Less 将采用两个值 a 和 b(以及我刚刚创建的辅助映射),当且仅当 a 在 s(目标列表)中的 b 之前返回 true:
function less(a, b, positions) {
return positions[a] < positions[b];
}
现在我不会描述如何在a 中找到关于该比较运算符的最大递增子序列。您可以查看this question 以了解如何执行此操作的详细说明。我将简单地假设我定义了一个函数:
function max_increasing_subsequence(a, positions)
这将返回a 中相对于上面定义的比较运算符less(使用positions)作为列表的最大递增子序列。我将使用您的第二个示例来说明我们目前所拥有的:
A = [9,1,2,3,0]
S = [0,1,2,3,9]
位置的值如下:
positions = { 0 : 0,
1 : 1,
2 : 2,
3 : 3,
9 : 4}
max_increasing_subsequence(a, positions) 的结果将是[1, 2, 3]。顺便说一句,如果a 中可能有重复元素,最好返回索引而不是max_increasing_subsequence 中的元素(在此特定示例中,差异将不可见)。
现在我将创建另一个辅助映射来指示最大递增子序列中包含哪些元素:
var included = {};
l = max_increasing_subsequence(a, positions);
for (var i = 0; i < l.length; ++i) {
included[l[i]] = true;
}
现在您可以在s 上进行一次迭代来完成解决方案。我将为最后一个元素添加一个特殊情况,以使代码更易于理解:
if (!(s[s.length - 1] in included)) {
console.log("Move" + s[s.length - 1] + " at the end");
}
for (var i = s.length - 2; i >= 0; --i) {
if (!(s[i] in included)) {
console.log("Move" + s[i] + " before " + s[i + 1]);
}
}
请注意,在上面的解决方案中,我假设每次您记录一个新命令时,您都会在所有先前的命令都已执行之后,按照数组 a 的顺序记录它。
所以总的来说,我相信变换应该是这样的:
function transform(a, s) {
var positions = {};
for (var i = 0; i < a.length; ++i) {
positions[a[i]] = i;
}
var included = {};
l = max_increasing_subsequence(a, positions);
var included = {};
for (var i = 0; i < l.length; ++i) {
included[l[i]] = true;
}
if (!(s[s.length - 1] in included)) {
console.log("Move" + s[s.length - 1] + " at the end");
}
for (var i = s.length - 2; i >= 0; --i) { // note s.length - 2 - don't process last element
if (!(s[i] in included)) {
console.log("Move" + s[i] + " before " + s[i + 1]);
}
}
}
我希望这段代码能让我的答案更清楚。