动态编程:如何将其分解为子问题?

时间:2019-07-11 09:50:22

标签: python-3.x algorithm dynamic-programming

我陷入了这个问题,需要一些帮助:

给定一个数组arr,在每一步中,除了该数组的一项以外,必须递增 1、2或5个单位(所有单元的单位数量相同) )。目的是找出所有项目的最小步数相等。

第一个示例

arr = [1, 1, 5]

1) [1 (+2), 1 (+2), 5] = [3, 3, 5]
2) [3 (+2), 3 (+2), 5] = [5, 5, 5]

解决方案:2个步骤


第二个示例

arr = [2, 2, 3, 7]

1) [2 (+1), 2 (+1), 3, 7 (+1)] = [3, 3, 3, 8]
2) [3 (+5), 3 (+5), 3 (+5), 8] = [8, 8, 8, 8]

解决方案:2个步骤


我尝试了一些事情,但是我真的很卡住。

我认为所有项目都相等时的基本情况。在另一种情况下,我尝试通过对数组中除一项以外的所有项增加1、2和5来找到所有可能的解决方案:

def equal(arr):

    if (allElementsIdentical(arr)):
        return 0

    min = sys.maxsize
    for i in [1, 2, 5]:
        for j in range(len(arr)):

            #Increment all items by "i", except item at index "j"
            newArr = arr.copy()
            for k in range(j):
                newArr[k] += i
            for k in range(j + 1, len(newArr)):
                newArr[k] += i

            movements = 1 + equal(newArr)
            if movements < min:
                min = movements
    return min

此解决方案无效,因为递归永远不会结束。例如

[1, 1, 5] -> [1, 2, 6] -> [1, 3, 7] -> [1, 4, 8] -> [1, 5, 9] -> ...

我最初的方法正确吗?如何正确地将其分解为子问题?如何获得重复关系?

(我正在学习Python,因此也欢迎对语法进行任何评论)

2 个答案:

答案 0 :(得分:1)

我们想用产生相同答案但更容易评估的问题代替这个问题。

使用动态编程方法使评估更容易的技巧是在很多地方显示相同的结果。因此,我们必须用规范化版本替换该问题的等效版本。

首先,答案不取决于数组元素所在的顺序。因此,我们可以将数组替换为从最小到最大排序的数组。现在,操作是将x添加到除一个之外的所有内容,然后重新排序为规范形式。

接下来,答案不取决于最小元素的值。因此,我们可以从所有条目中减去该值。现在的操作是,将x添加到除一个以外的所有内容中,然后重新排序为规范形式,然后从所有内容中减去最小的内容。

这大大减少了我们的问题。广度优先搜索足以胜任。但是,我们还有另一招。这个技巧是,应用操作的顺序无关紧要。因此,我们可以在执行2个操作之前先执行所有5个操作,然后再执行1个操作。有了这个技巧,我们可以用(node, last_operation)和开始的last_operation替换为5的每个规范化节点。之所以会获胜,是因为我们现在对其余A *搜索有一个上限。该界限是当前步数+ ceil(node[i] / last_operation)的总和。

现在这可以通过A *搜索解决。让我们手工做您的例子。使用符号(total cost, normalized, last_operation, steps)

示例1:[1, 1, 5]

我们归一化为[0, 0, 4],并且last_operation为5,成本为0+0+1 = 1。没有采取任何步骤。所以我们开始:

(1, [0, 0, 4], 5)

我们将其淘汰,并考虑我们的运营。对于操作5,我们得到以下信息:

[0, 0, 4] + [5, 5, 0] = [5, 5, 4] => [0, 1, 1] # cost 1 + 0+1+1 = 3
[0, 0, 4] + [5, 0, 5] = [5, 0, 9] => [0, 5, 9] # cost 1 + 0+1+2 = 4
[0, 0, 4] + [0, 5, 5] = [0, 5, 9] => [0, 5, 9] # cost 1 + 0+1+2 = 4 DUP

对于操作2,我们得到:

[0, 0, 4] + [2, 2, 0] = [2, 2, 4] => [0, 0, 2] # cost 1 + 0+0+1 = 2
[0, 0, 4] + [2, 0, 2] = [2, 0, 4] => [0, 2, 4] # cost 1 + 0+1+2 = 4
[0, 0, 4] + [0, 2, 2] = [0, 2, 4] => [0, 2, 4] # cost 1 + 0+1+2 = 4 DUP

对于操作1,我们得到:

[0, 0, 4] + [1, 1, 0] = [1, 1, 4] => [0, 0, 3] # cost 1 + 0+0+3 = 4
[0, 0, 4] + [1, 0, 1] = [1, 0, 4] => [0, 1, 4] # cost 1 + 0+1+5 = 6
[0, 0, 4] + [0, 1, 1] = [0, 1, 4] => [0, 1, 4] # cost 1 + 0+1+5 = 6 DUP

我们将7个非重复项放入优先级队列,然后出现的最好结果如下:

(total cost, normalized, last_operation, steps)
(         2,    [0,0,2],              2,     1)

然后我们尝试对此进行操作21,然后发现其中的结果之一是经过2个步骤后的[0, 0, 0]

答案 1 :(得分:0)

对我来说,除了一个元素外,向所有元素加1、2或5似乎比从一个元素中减去1、2或5容易得多。

[1, 1, 5] -> 5 - 2 -> 3 - 2

[2, 2, 3, 7] -> 3 - 1 -> 7 - 5

要构造一个递归,我们可以使用直方图,并考虑到移动任何值都将花费其操作频率。由于我们可以减少1,因此我们可以轻松地为可能需要将所有值移至的最低目标设置下限。由于任何其他值都可以达到最低值,因此将所有值都向下移动到(lowest-5)(如HackerRank editorial所述),与将所有元素向下移动到最低值相比,将需要进行更多n操作,因为我们首先将所有元素移到最低,然后对每个元素应用(-5)。

社论还指出,贪婪的人在O(1)中可以找到将k转移到目标0的最小操作数x

k = x / 5 + (x % 5) / 2 + (x % 5) % 2

由于您被要求尝试重现一次,因此在这种情况下,我们将为直方图中的每个值解决硬币更改问题(硬币[5,2,1])以达到目标。它们是独立的:我们对每个值应用coin_change来查找所需操作数的顺序没有区别,然后将其乘以值的频率。计算所有值以达到每个目标的总操作量,我们选择的最少。