将列表转换为所需数组的最小步骤算法。 (仅使用InsertAt和DeleteAt)

时间:2016-12-16 21:26:09

标签: c arrays algorithm list

情况

首先,你有一个数组/列表A,然后你想将它转换为给定的预期数组/列表B.您可以在数组上应用的唯一操作是InsertAtDeleteAt,他们可以在列表中的某个索引处插入和删除元素。

  

注意:阵列B总是排序,而阵列A可能不是。

例如,您有一个[1, 4, 3, 6, 7]

的数组A.

,您希望它成为[2, 3, 4, 5, 6, 6, 7, 8]

这样做的一种方法是让A接受以下行动:

    deleteAt(0); // which will delete element 1, arrayA now [4, 3, 6, 7]
    deleteAt(0); // delete element 4 which now at index 0
                 // array A now [3, 6, 7]
    insertAt(0, 2); // Insert value to at index 0 of array A
                    // array A now [2, 3, 6, 7]
    insertAt(2, 4); // array now [2, 3, 4, 6, 7]
    insertAt(3, 5); // array Now [2, 3, 4, 5, 6, 7]
    insertAt(5, 6); // array Now [2, 3, 4, 5, 6, 6, 7]
    insertAt(7, 8); // array Now [2, 3, 4, 5, 6, 6, 7, 8]

在上面的例子中,对阵列A进行了7次操作,将其转换为我们想要的数组。

因此,我们如何找到将A转换为B的步骤以及最小步骤?谢谢!

btw,删除A处所有元素的解决方案然后添加从B到A的所有内容仅适用于A& B没有任何共同点。

我的想法

到目前为止我做了什么:

  1. 比较阵列A和阵列B,然后保存删除阵列A中无法在阵列B中找到的所有元素。
  2. 从A和B的公共列表中找到增长最长的子序列。
  3. 删除不在最长的子序列中的所有元素。
  4. 比较剩下的B,然后相应地添加元素。
  5. 然而,我正在努力实施......

    更改日志

    1. 修正了丢失元素7的拼写错误,现在最少的操作是7。
    2. 添加了MY THOUGHTS部分
    3. 有一个答案详细阐述了Levenshtein距离(AKA最小编辑距离),不知何故它消失了..但我发现在阅读git / git levenshtein.c文件之后真的很有用,它似乎是一个更快的算法然后我已经有。但是,我不确定该算法会给我详细的步骤,或者它只能给出最少数量的步骤。

2 个答案:

答案 0 :(得分:2)

我有一个似乎有效的python程序,但它不是很短

__version__ = '0.2.0'

class Impossible(RuntimeError): pass

deleteAt = 'deleteAt'
insertAt = 'insertAt' 
incOffset = 'incOffset'

def remove_all(size):
    return [(deleteAt, i, None) for i in range(size-1, -1, -1)]

def index_not(seq, val):
    for i, x in enumerate(seq):
        if x != val:
            return i
    return len(seq)

def cnt_checked(func):
    """Helper function to check some function's contract"""
    from functools import wraps
    @wraps(func)
    def wrapper(src, dst, maxsteps):
        nsteps, steps = func(src, dst, maxsteps)
        if nsteps > maxsteps:
            raise RuntimeError(('cnt_checked() failed', maxsteps, nsteps))
        return nsteps, steps
    return wrapper

@cnt_checked
def strategy_1(src, dst, maxsteps):
    # get dst's first value from src
    val = dst[0]
    try:
        index = src.index(val)
    except ValueError:
        raise Impossible

    # remove all items in src before val's first occurrence
    left_steps = remove_all(index)
    src = src[index:]
    n = min(index_not(src, val), index_not(dst, val))
    score = len(left_steps)
    assert n > 0
    left_steps.append([incOffset, n, None])
    right_steps = [[incOffset, -n, None]]
    nsteps, steps = rec_find_path(src[n:], dst[n:], maxsteps - score)
    return (score + nsteps, (left_steps + steps + right_steps))

@cnt_checked
def strategy_2(src, dst, maxsteps):
    # do not get dst's first value from src
    val = dst[0]
    left_steps = []
    src = list(src)
    for i in range(len(src)-1, -1, -1):
        if src[i] == val:
            left_steps.append((deleteAt, i, None))
            del src[i]
    n = index_not(dst, val)
    right_steps = [(insertAt, 0, val) for i in range(n)]
    dst = dst[n:]
    score = len(left_steps) + len(right_steps)
    nsteps, steps = rec_find_path(src, dst, maxsteps - score)
    return (score + nsteps, (left_steps + steps + right_steps))

@cnt_checked
def rec_find_path(src, dst, maxsteps):

    if maxsteps <= 0:
        if (maxsteps == 0) and (src == dst):
            return (0, [])
        else:
            raise Impossible

    # if destination is empty, clear source
    if not dst:
        if len(src) > maxsteps:
            raise Impossible
        steps = remove_all(len(src))
        return (len(steps), steps)

    found = False
    try:
        nsteps_1, steps_1 = strategy_1(src, dst, maxsteps)
    except Impossible:
        pass
    else:
        found = True
        maxsteps = nsteps_1 - 1
    try:
        nsteps_2, steps_2 = strategy_2(src, dst, maxsteps)
    except Impossible:
        if found:
            return (nsteps_1, steps_1)
        else:
            raise
    else:
        return (nsteps_2, steps_2)

def find_path(A, B):
    assert B == list(sorted(B))
    maxsteps = len(A) + len(B)
    nsteps, steps = rec_find_path(A, B, maxsteps)
    result = []
    offset = 0
    for a, b, c in steps:
        if a == incOffset:
            offset += b
        else:
            result.append((a, b + offset, c))
    return result

def check(start, target, ops):
    """Helper function to check correctness of solution"""
    L = list(start)
    for a, b, c in ops:
        print(L)
        if a == insertAt:
            L.insert(b, c)
        elif a == deleteAt:
            del L[b]
        else:
            raise RuntimeError(('Unexpected op:', a))
    print(L)
    if L != target:
        raise RuntimeError(('Result check failed, expected', target, 'got:', L))

start = [1, 4, 3, 6, 7]
target = [2, 3, 4, 5, 6, 6, 7, 8]

ops = find_path(start, target)
print(ops)

check(start, target, ops)

使用此代码进行一些测试后,结果显而易见 两阶段运作。第一阶段是删除项目 最初的清单,除了增加的所有项目序列之外的所有项目 目标列表(重复)。然后将缺失的项目添加到列表中 目标列表已建成。

临时结论是,如果我们找到一个算法来确定 最初出现在目标列表中的项目的最长子序列 第一个列表,按相同顺序但不一定是连续的,然后它给出最短路径。这是一个新的 而且可能更简单的问题。这可能就是你上面所说的,但从程序的输出中可以更清楚。

似乎很明显,这个问题可以归结为longest increasing subsequence

的问题

答案 1 :(得分:1)

我们可以很容易地证明,如果A中的元素集合不值得删除并且数量大于最长非元素,则问题会减少到最长的非递减子序列在A中也减少了B个元素中的子序列,那么此集合的所有元素必须在B中以相同的顺序存在,这意味着它是一个更长的非递减子序列,并且与我们的断言相矛盾。此外,A中任何不值得删除的较小集合必须以B的顺序存在于同一顺序中,因此也是A元素中最长的非递减子序列的一部分。 B

然后算法缩减为最长的子序列问题O(n log n + m)

(1) Find the longest non-decreasing subsequence of elements
    in A that have at least the same count in B.

(2) All other items in A are to be deleted and the 
    missing elements from B added.