假设我们有两个整数数组,例如:
var A = new int[] {1, 4, 6, 12, 44};
var B = new int[] {2, 4, 6, 44, 45};
问题在于将过渡步骤描述为:
从数组A
开始,然后继续执行以下操作:
remove index = 0 // result = {4, 6, 12, 44}
insert index = 0 value = 2 // result = {2, 4, 6, 12, 44}
remove index = 3 // result = {2, 4, 6, 44}
insert index = 4 value = 45 // result = {2, 4, 6, 44, 45}
现在我们有了数组B
我的问题是:如何以给定数组A和B以编程方式生成这些步骤的任何编程语言或伪代码设计此算法?
常见步骤结构如下:
步骤N:insert/remove index = i [value = v]
当然,从A
中删除所有元素,然后从B
中插入所有元素是一种解决方案,并且对于给定的A
和{{1},也许会有不止一种解决方案},但我希望以最少的步骤进行过渡。
答案 0 :(得分:0)
有Levenshtein distance的概念。它用于比较两个字符串(但是您也可以将相同的原理应用于整数数组):最少的编辑次数即可将一个字符串更改为另一个字符串,其中一个编辑是删除,插入或替换。
使用动态编程可以有效地解决该问题。 Wiki文章显示了这种方法。 您可以对问题使用相同的方法。但是由于在您的情况下不允许您替换元素(仅允许删除和插入操作),因此您甚至可以简化递归a(微小)位:
def edit_distance(s, len_s, t, len_t):
if len_s == 0:
return len_t
if len_t == 0:
return len_s
if s[len_s-1] == t[len_t-1]:
return edit_distance(s, len_s-1, t, len_t-1)
else:
return min(edit_distance(s, len_s-1, t, len_t) + 1,
edit_distance(s, len_s, t, len_t-1) + 1)
上面的代码没有动态编程。要使其高效,您需要添加它。
此外,该代码将只计算步骤数。如果您还想列出这些步骤,则必须存储完整的表并回溯解决方案。
该方法的时间和内存复杂度:O(len_s * len_t)。
以下是使用两个数组[1, 4, 6, 12, 44]
和[2, 4, 6, 44, 45]
的示例。如果您对字符串的每种可能的前缀组合应用动态编程(例如,采用自下而上的方法),则会获得此表。
0 1 2 3 4 5
1 2 3 4 5 6
2 3 2 3 4 5
3 4 3 2 3 4
4 5 4 3 4 5
5 6 5 4 3 4
在右下角,我们看到4是使两个数组相等的最佳步数。现在我们可以回溯并再次查看递归公式。由于最后两个元素不相等,因此必须是插入/删除操作。我们在表中可以看到,[1, 4, 6, 12], [2, 4, 6, 44, 45]
的最佳步骤数是5
,而[1, 4, 6, 12, 44], [2, 4, 6, 44]
的最佳步骤数是3
。因此,这里的最佳做法是删除第二个数组的最后一个元素,或者换句话说,在第一个数组中插入45
。
现在,我们可以了解产生[1, 4, 6, 12, 44], [2, 4, 6, 44]
的最后一步了。由于最后一个元素相等,因此步骤很明确。我们将它们都保留,并且不执行插入或删除操作。
那么[1, 4, 6, 12], [2, 4, 6]
中的最后一步是什么?该表显示最佳值3源自位置[1, 4, 6], [2, 4, 6]
,这意味着在第一个数组中删除了12
。
以此类推。
有趣的是,可以有多个最佳解决方案。在这里,我向您展示一条可能的路径(与您的解决方案完全对应):
0-1 2 3 4 5
|
1 2 3 4 5 6
\
2 3 2 3 4 5
\
3 4 3 2 3 4
|
4 5 4 3 4 5
\
5 6 5 4 3-4