我一直在研究一些Project Euler问题,而且在大多数情况下,我一直都做得很好。问题18虽然让我很难过。
从树的顶部开始,我应该找到导致最大总和的路径
3
7 4
2 4 6
8 5 9 3
在这种情况下,有24条可能的路径,或4条!最好的路径是 3 - > 7 - > 4 - > 9 ,总计达到23。
我尝试通过复制示例来解决问题。
array = [[3],[7,4],[2,4,6],[8,5,9,3]]
array.each_slice(1){|s|p s} => This prints the tree
我得到了一个答案,在极少数情况下这是正确的,但这并不合法。
sum = []
array.each{|a|sum.push(a.sample)}
return sum
这种方法基本上只是选择一个随机路径,即使有这个简单的例子,仍然只有十分之一的机会才能做到正确。
我尝试过像
这样的事情level_two = []
level_three = []
for i in 0..array.length-1
for j in 0..array[1].length-1
level_two.push([array[0], array[1][i]) => Now level 2 has 2 paths - [3,7] & [3,4]
for k in 0..array[2].length-1
level_three.push([level_two[0],array[2][k], [level_two[1], array[2][k])
end
end
end
但此时我甚至无法追踪我正在使用的变量。
我尝试过each_cons,组合和zip,但没有一个有效。
有谁知道解决这个问题的策略?
编辑:这不会给出问题的答案,而只是答案示例。然后,我将在主要问题上应用该设计模式。
答案 0 :(得分:2)
此问题是递归的完美用例。对于术语,让我们说T[3][0]
是从0开始计算的第3行中的第0个元素(在您的示例中为8)。
从树的顶部开始,导致最大总和的路径是什么?首先,让我们稍微改写一下:从T[0][0]
开始的最大总和的路径是什么?那么T[0][0]
加上从T[1][0]
或T[1][1]
开始的路径(无论哪条路径更大)。寻找路径本身是偶然找到最大的总和,所以我们只关注它。
让我们说S[x][y]
是从T[x][y]
开始的路径的最大总和。然后从之前的观察我们得到
S[x][y] = T[x][y] + MAX(S[x + 1][y], S[x + 1][y + 1])
除非x
是树的最后一行。让R
成为树的最后一行,在这种情况下
S[R][y] = T[R][y]
在Ruby中,我们可以编写如下函数:
def largest_sum_path(tree, x = 0, y = 0)
path = [tree[x][y]]
return path if x == tree.length - 1
left = largest_sum_path(tree, x + 1, y)
right = largest_sum_path(tree, x + 1, y + 1)
return path.concat([left, right].max_by { |p| p.reduce(:+) })
end
然而,我怀疑这种方法对于大树来说非常慢。我会留给您看看如何优化它。
答案 1 :(得分:2)
对于这些类型的问题,我的第一步是理解数组索引之间的关系。给定此树的 n 级别的数组,数组级别 n + 1 的子元素可以是索引 i 的父元素(在数组中)级别 n )访问?答案是 i 和 i + 1 的元素。
例如:设[2, 4, 6]
为2
级别的数组,[8, 5, 9, 3]
为级别3
的数组。元素6
(索引2
)访问哪些子元素?可以访问9, 3
,因为它们的索引为2, 3
。
现在我们可以为这个问题创建一个递归解决方案:
@res = [] # All paths will be stored here
def paths(arr, temp, lvl = 0, idx = 0)
if arr[lvl] # we haven't hit end of path
# loop through elements at i and i + 1
arr[lvl][idx..idx+1].each_with_index do |x, i|
# go one level deeper into tree
paths(arr, temp.dup << x, lvl + 1, i)
end
else
@res << temp # `temp` has complete path, push it into results
end
end
paths(array, []) # initialize temporary path to empty array
@res.map { |x| x.reduce(:+) }.max # => 23
答案 2 :(得分:1)
从最后一行开始。将其替换为由每个连续对的最大值组成的行,因此8 5 9 3
- &gt; 8 9 9
。将这些值添加到其上方的行中(2 4 6 - > 10 13 15)。重复直到第一行。(10 13 15 - &gt; 13 15,7 4→20 19 - > 20; 3 - &gt; 23.
答案 3 :(得分:1)
我就是这样做的。
<强>代码强>
def longest_path(arr)
return nil if arr.empty?
h = { len: arr.first.first, path: [] }
return h if arr.size == 1
arr[1..-1].reduce([h]) do |l,row|
h = l.first
left = { len: h[:len]+row.first, path: h[:path]+[:l] }
mid = l.each_cons(2).to_a.zip(row[1..-2]).map do |(h1,h2),v|
if h1[:len] >= h2[:len]
{ len: h1[:len]+v, path: h1[:path]+[:r] }
else
{ len: h2[:len]+v, path: h2[:path]+[:l] }
end
end
h = l.last
right = { len: h[:len]+row.last, path: h[:path]+[:r] }
[left, *mid, right]
end.max_by { |h| h[:len] }
end
示例强>
a = [ [3],
[7,4],
[2,4,6],
[8,5,9,3]]
longest_path a
#=> {:len=>23, :path=>[:l, :r, :r]}
因此,最长路径的长度为23.从第一行中的3
,第二行中的向下和向左(:l
)到7
,向下和向右({{ 1}})至第三行中的:r
,以及最后一行中4
的向下和向右:9
。
<强>解释强>
这个问题同样是算法的实现作为算法的选择。在我看来,后者是相当明显的:求解一行,用它来解决两行,依此类推。
考虑上面的示例数组3+7+4+9 => 23
。
a
作为
arr = a
arr.empty? #=> false, so continue
h = { len: arr.first.first, path: [] }
#=> {:len=>3, :path=>[]}
return h if arr.size == 1 # arr.size => 4, so continue
arr[1..-1] => [[7, 4], [2, 4, 6], [8, 5, 9, 3]]
将reduce
和[h]
传递到块中并分配块变量:
[7, 4]
然后计算:
l = [{ len: arr.first.first, path: [] }]
row = [7, 4]
h = l.first
#=> {:len=>3, :path=>[]}
left = { len: h[:len]+row.first, path: h[:path]+[:l] }
#=> {:len=>10, :path=>[:l]}
mid = []
h = l.last
#=> {:len=>3, :path=>[]}
right = { len: h[:len]+row.last, path: h[:path]+[:r] }
#=> {:len=>7, :path=>[:r]}
[left, *mid, right]
#=> [{:len=>10, :path=>[:l]}, {:len=>7, :path=>[:r]}]
因为mid => []
在大小为1的数组上执行。
上面的最后一行提供了第二行中两个元素中每个元素的最长路径的信息。对于第一个元素(each_cons(2)
),路径长度为7
,并且从第一行中的唯一元素(10
)开始,然后向下和“向左”({{1 }})给定的元素。
在3
块的最后一行计算:l
时,块变量[left, *mid, right]
被赋予该值以处理下一行reduce
:
l
接下来我们计算以下内容:
arr
l = [{:len=>10, :path=>[:l]}, {:len=>7, :path=>[:r]}]
row = [2, 4, 6]
和left = { len: h[:len]+row.first, path: h[:path]+[:l] }
#=> {:len=>5, :path=>[:l]}
l.each_cons(2).to_a.zip(row[1..-2]).map do |(h1,h2),v|
if h1[:len] >= h2[:len]
{ len: h1[:len]+v, path: h1[:path]+[:r] }
else
{ len: h2[:len]+v, path: h2[:path]+[:l] }
end
end
#=> [{:len=>14, :path=>[:l, :r]}]
h = l.last
#=> {:len=>7, :path=>[:r]}
right = { len: h[:len]+row.last, path: h[:path]+[:r] }
#=> {:len=>13, :path=>[:r, :r]}
[left, *mid, right]
#=> [{:len=>5, :path=>[:l]}, {:len=>14, :path=>[:l, :r]},
# {:len=>13, :path=>[:r, :r]}]
的计算与前一个left
元素的计算类似。我们来看看right
的计算:
arr
mid
是一个包含一个元素的数组。该元素被传递到pairs = l.each_cons(2).to_a
#=> [[{:len=>10, :path=>[:l]}, {:len=>7, :path=>[:r]}]]
vals = pairs.zip(row[1..-2])
#=> pairs.zip([4])
#=> [[[{:len=>10, :path=>[:l]}, {:len=>7, :path=>[:r]}], 4]]
,被分解并分配给块变量:
vals
作为map
,我们执行:
h1 = {:len=>10, :path=>[:l]}
h2 = {:len=> 7, :path=>[:r]}
v = 4
h1[:len] #=> 10
h2[:len] #=> 7
这是10 > 7
的值。 { len: h1[:len]+v, path: h1[:path]+[:r] }
的块值mid
现已分配l
的结果:
reduce
开始处理第三行。 [left, *mid, right]
返回:
l = [{:len=> 5, :path=>[:l]}, {:len=>14, :path=>[:l, :r]},
{:len=>13, :path=>[:r, :r]}]
提供描述最后一行每个元素的最长路径的信息。最后一步是:
reduce