此代码遍历数组,并返回最大的不相邻整数之和。这是来自HackerRank-有人可以解释为什么这样做吗?这是我在网上找到的解决方案,但我不了解,自己也没弄清楚。
谢谢!
def maxSubsetSum(arr)
incl = 0
excl = 0
temp = 0
for i in 0...arr.size
temp = incl
incl = [arr[i]+excl, temp].max
excl = temp
end
return [incl, excl].max
end
maxSubsetSum([1,3,5,2,4,6,8])
答案 0 :(得分:0)
这是一些非常丑陋的代码,所以让我们在继续之前对其进行清理:
def maxSubsetSum(arr)
incl = 0
excl = 0
temp = 0
arr.each do |value|
temp = incl
incl = [value + excl, temp].max
excl = temp
end
[incl, excl].max
end
maxSubsetSum([1,3,5,2,4,6,8])
现在我们可以开始分析此代码了。我遍历并在循环的每个步骤中写了每个变量的值:
value = 1
temp = 0
incl = 1
excl = 0
value = 3
temp = 1
incl = 3
excl = 1
value = 5
temp = 3
incl = 6
excl = 3
value = 2
temp = 6
incl = 6
excl = 6
value = 4
temp = 6
incl = 10
excl = 6
value = 6
temp = 10
incl = 12
excl = 10
value = 8
temp = 12
incl = 18
excl = 12
(return 18)
在任何给定的点,程序都在确定是否应“使用”值-使用值的缺点是您不能在其后使用该值,因为它是相邻的。在此过程的每个步骤中,它都是将当前值与excl
(从技术上为incl
到temp
(在上一个步骤中最好的总和,不包括该值)进行比较。 1}}在该阶段保留temp
,代表包含该值的上一次迭代中的值。
incl
不会在循环之间被记住;在循环的每次迭代之后,仅重要的值为temp
和incl
。重申一下,在每个循环的结尾,excl
保留包括前一个数字的最佳和,而incl
保留不包括先前的数字的最佳和。在循环的每个步骤中,都会重新计算incl和excl以反映新值的包含或排除。
为证明此过程确实有效,让我们考虑上面的数组,但末尾有一个额外的元素7。因此,我们的数组如下所示:excl
。我们已经完成了上一个清单中的大部分工作。我们将[1,3,5,2,4,6,8,7]
设置为temp
,18
变成incl
,即[7 + 12, 18].max
,而19
变成excl
。现在我们可以看到,包含最后一个数字意味着我们得到的数字要比先前的结果大,因此我们必须使用它,这意味着我们不能使用先前用于获得结果的18
此过程称为动态编程,其中为了确定整个问题的答案,您可以分解问题并增加复杂性。在这种情况下,我们分解数组,然后慢慢地将每个值加回去,并跟踪上一部分的最佳结果。
答案 1 :(得分:0)
了解算法
一旦您了解所使用的算法,代码就非常简单。谷歌搜索发现了很多热门。参见,例如,this article。他们似乎都使用相同的动态编程方法。
一般解决方案会同时产生最大和和原始数组中产生该和的元素数组。这里只需要总和,因此解决方案略有简化。我将首先描述产生一般解的算法,然后简化以仅计算最大和。
计算常规解决方案
如果数组为a
,则第n
步将计算两个值:前n
个元素a[0]
,a[1]
, ...,a[n-1]
,如果包含第n
个元素,则最大和不包含该元素。这很容易,因为已经为上一个元素a[n-2]
计算了这两个结果。处理完数组的最后一个元素后,最大和等于包含最后一个元素时的最大和与排除最后一个元素时的最大和中的较大者。为了构造产生最大和的数组,采用了简单的后退方法。
让b
是我们将要构建的哈希数组。最初,
b[0] = { included: 0, excluded: 0 }
然后每个n = 1,..., m
,其中m = a.size
,
b[n] = { included: a[n-1] + b[n-1][:excluded],
excluded: [b[n-1][:included], b[n-1][:excluded]].max }
计算b[m]
之后,最大总数等于
[b[m][:included], b[m][:excluded]].max
在下面的示例中概述了构造可产生最大和的数组的后退操作。
请考虑以下内容。
arr = [1, 3, -2, 7, 4, 6, 8, -3, 2]
b = (1..arr.size).each_with_object([{ included: 0, excluded: 0 }]) do |n,b|
b[n] = { included: arr[n-1] + b[n-1][:excluded],
excluded: [b[n-1][:included], b[n-1][:excluded]].max }
end
#=> [{:included=> 0, :excluded=> 0}, n arr[n-1]
# {:included=> 1, :excluded=> 0}, 1 1
# {:included=> 3, :excluded=> 1}, 2 3
# {:included=>-1, :excluded=> 3}, 3 -2
# {:included=>10, :excluded=> 3}, 4 7
# {:included=> 7, :excluded=>10}, 5 4
# {:included=>16, :excluded=>10}, 6 6
# {:included=>18, :excluded=>16}, 7 8
# {:included=>13, :excluded=>18}, 8 -3
# {:included=>20, :excluded=>18}] 9 2
我为上面每个arr[-1]
的值都包含了n
,以方便参考。可以看到最大的总和是[20, 18].max #=> 20
,其中包括最后一个元素arr[9] #=> 2
。因此,不能包含arr[8]
。请注意,b[8][:excluded] + arr[8] #=> 18 + 2 => 20
,即b[9][:included]
。
由于排除了arr[8]
,因此可以包括或排除arr[7]
。我们看到b[7][:included] == b[8][:excluded] == 18
和b[7][:excluded] == 16 < 18 == b[8][:exclued]
,这告诉我们包含了arr[7]
。使用相同的推理可以返回到b
的开头,表明构成数组的20
的元素是[3, 7, 8, 2]
。通常,当然可以有多个最优解。
仅计算最大金额
如果如本问题所述,仅需要最大和,而不需要产生最大和的元素数组,则不需要b
是一个数组。我们可以简单地写
b = { included: a[0], excluded: 0 }
然后每个n = 1,..., m-1
b[:included], b[:excluded] =
a[n] + b[:excluded], [b[:included], b[:excluded]].max
我们可以将其包装为以下方法。
def max_subset_sum(arr)
arr.size.times.with_object({ included: 0, excluded: 0 }) do |n,h|
h[:included], h[:excluded] =
arr[n] + h[:excluded], [h[:included], h[:excluded]].max
end.values.max
end
max_subset_sum arr
#=> 20 (= 3+7+8+2)
如果需要的话,我们可以用两个元素的数组而不是哈希(更接近问题中的代码)来编写它,尽管我认为这不太清楚。
def optimize(arr)
arr.size.times.with_object([0, 0]) do |n,a|
a[0], a[1] = arr[n] + a[1], [a[0], a[1]].max
end.max
end
optimize arr
#=> 20
请注意,我已使用并行分配来避免创建临时变量。