这个问题等同于
最长上升子序列问题。
你需要定义一个比较运算符
less
。
less(a, b)
将在目标序列中只有当
a
在
b
之前时返回
true
。现在,使用此比较运算符,计算源序列的最大递增子序列。您将需要移动每个不属于该子序列的元素(否则子序列将不是最大的),并且您可以将其移动一次(将其移动到其目标位置)。
编辑:根据amit的要求,这里是我对上述陈述的证明:
我们将目标序列表示为
B
,将源序列表示为
A
。设
n=|A|
,设
k
为如上所述的最长递增序列的长度。
假设可以用比
n-k
更少的步骤从
A
到达
B
。 这意味着至少有
n-k+1
个元素不会移动。让 s
1,s
2,...s
m 成为不被移动的元素集合。 根据假设,我们知道
m > k
。由于这些元素未移动,因此它们相对于彼此的位置不能改变。因此,所有这些元素在目标序列
B
中的相对位置与
A
中的位置相同。 因此,对于任何
i
,
j
,定义中的 less(s
i, s
j) 运算符都应该成立。但是,如果这是真的,那么 s
1,s
2,...s
m 就构成了递增序列,而且因为
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,如我上面的答案所述。Less将使用两个值a和b(以及我刚创建的辅助映射),仅当a在s(目标列表)中排在b之前时返回true:
function less(a, b, positions) {
return positions[a] < positions[b];
}
现在我不会描述如何找到相对于该比较运算符在a
中找到最大递增子序列。你可以查看这个问题以获得详细的解释。我将简单地假设我已经定义了一个函数:
function max_increasing_subsequence(a, positions)
这将根据上述使用positions
定义的比较运算符less
,返回列表形式的a
中最大递增子序列。我将使用您提供的第二个示例来说明我们目前的情况:
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
的顺序,在执行所有先前的命令后进行记录。
因此,总体而言,我认为transform应该是这样的:
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) {
if (!(s[i] in included)) {
console.log("Move" + s[i] + " before " + s[i + 1]);
}
}
}
我希望这段代码能让我的回答更清晰。
A = S
怎么样?在我猜测的每种现代编程语言中都可以工作,其中未排序和已排序列表具有共同的基类。 - Martin Meesersource=A
和target=S
,使用包含所有排列的无权图,边是一种可能的移动。使用BFS算法的解决方案的复杂度将为O(n^d)
,或者使用双向BFS的复杂度将为O(n^(d/2))
- 其中d
是所需的最小“移动”次数。 - amit