【问题标题】:Check if numbers summed in array match input parameter检查数组中求和的数字是否与输入参数匹配
【发布时间】:2017-04-28 00:41:56
【问题描述】:

我正在关注一个网站上的 Ruby 练习问题,但我完全无法解决这个问题。基本上,给定一个函数has_sum?(val, arr),如果任何数组中的数字组合(第二个参数)可以加在一起等于第一个参数,则返回true,否则返回false。所以:

has_sum?(5, [1, 2, 3, 4]) # true

has_sum?(5, [1, 2, 6]) # false

我完全被卡住了,不太确定如何完成这个......这是我到目前为止所拥有的。

def has_sum?(val, arr)
  arr.each_with_index do |idx, v|
    # ??? no idea what to do here except add the current num to the next in the list
  end
end

任何帮助将不胜感激 - 谢谢!

【问题讨论】:

  • 没有提到这一点,但肯定会很有趣!
  • 我错了。我的回答并不要求所有元素都是非负数。

标签: ruby


【解决方案1】:

我会做嵌套循环。

for x = 0 to length of array
    for y = x + 1 to length of array
        if number at x + number at y = sum return true

return false

基本上它会检查每个数字的总和以及它后面的每个数字。

编辑:这一次只能求和 2 个数字。如果您希望能够对任意数量的数字求和,这是行不通的。

【讨论】:

  • 是的,这是我考虑过的一个实现,但它特别希望您能够对任意数量的数字求和。不过谢谢你。
【解决方案2】:

当有一个任意长度的子集加起来等于该和时,数组可以产生一个和:

def has_sum?(val, arr)
  (arr.size + 1).times
    .flat_map { |i| arr.combination(i).to_a }
    .any? { |s| s.inject(:+) == val }
end

has_sum?(5, [5])
# => true
has_sum?(5, [1, 2, 3])
# => true
has_sum?(5, [1, 1, 1, 1, 1, 1])
# => true
has_sum?(5, [1, 2, 7])
# => false

这不是很有效,因为它会在测试之前生成所有可能性。这应该更早结束:

def has_sum?(val, arr)
  (arr.size + 1).times.any? { |i|
    arr.combination(i).any? { |s| s.inject(:+) == val }
  }
end

更高效的是递归实现,其思想是空数组的总和为零(并且has_sum(nonzero, []) 应该返回 false);对于一个更大的数组,我们弹出它的头部,如果我们计算或不计算头部元素,看看数组其余部分的总和是否合适。在这里,我们不会一遍又一遍地对整个数组进行无用的求和:

def has_sum?(val, arr)
  if arr.empty?
    val.zero?
  else
    first, *rest = arr
    has_sum?(val, rest) || has_sum?(val - first, rest)
  end
end

【讨论】:

  • 我认为你不需要to_a
  • 哇,这太棒了,所以我想那里的combination 关键字会输出数组中所有可能的组合项?需要i + 1吗?
  • @CarySwoveland:试过没有to_a?我做到了。 combination 返回一个 Enumeratorflat_map 不会解包。
  • combination 输出给定大小的所有子集。我想测试从 1 到 N 的所有尺寸; times 给了我从 0 到 N-1 的数字。也就是说...最好从 0 到 N 进行测试,以便返回与第二个示例相同的结果(has_sum?(0, []) 为 true);所以我移动了+1 :)
  • 啊,是的,flat_map。在这种情况下,请考虑(1..arr.size).any? { |i| combination(i).any? { |a| { |s| s.inject(:+) == val } },我认为它会更有效,因为它可以更早终止。
【解决方案3】:

此解决方案采用动态规划。我假设已经从数组中删除了零。如果数组中的所有数字都是正数,我们也可以删除大于目标总和的元素。

代码

def sum_to_target(arr, target)
  h = arr.each_index.with_object({}) do |i,h|
    v = arr[i]
    h.keys.each do |n|
      unless h.key?(n+v) # || (n+v > target)
        h[n+v] = v
        return reconstruct(h, target) if n+v == target
      end
    end
    h[v] = v unless h.key?(v)
    return reconstruct(h, target) if v == target
  end
  nil
end

def reconstruct(h, target)
  a = []
  loop do
    i = h[target]
    a.unshift i
    target -= i
    return a if target == 0
   end
  a
end

如果arr 仅包含正值,则可能会进一步提高效率。1

示例

#1

sum_to_target [2,4,7,2], 8
  #=> [2, 4, 2]

#2

arr = [64, 18, 64, 6, 39, 51, 87, 62, 78, 62, 49, 86, 35, 57, 40, 15, 74, 10, 8, 7]
a = sum_to_target(arr, 461)
  #=> [64, 18, 39, 51, 87, 62, 78, 62]

让我们检查一下。

a.reduce(:+)
  #=> 461 

#3

a = sum_to_target([-64, 18, 64, -6, 39, 51, -87, 62, -78, 62, 49, 86, 35, 57, 
                    40, 15, -74, 10, -8, -7], 190)
  #=> [18, 64, -6, 39, 51, -87, 62, 49] 

a.reduce(:+)
  #=> 190

#4

arr = 1_000.times.map { rand 1..5_000 }
  #=> [3471, 1891, 4257, 2265, 832, 1060, 3961, 875, 614, 2308, 2240, 3286,
  #    ...
  #    521, 1316, 1986, 4099, 1398, 3803, 4498, 4607, 2262, 3941, 4367]

arr 是一个包含 1,000 个元素的数组,每个元素都是 1 到 5,000 之间的随机数。

answer = arr.sample(500)
  #=> [3469, 2957, 1542, 950, 4765, 3126, 3602, 755, 4132, 4281, 2374,
  #    ...
  #    427, 4238, 4397, 2717, 912, 1690, 3626, 169, 3607, 4084, 3161]

answer 是一个包含来自arr 的 500 个元素的数组,未经替换采样。

target = answer.reduce(:+)
  #=> 1_226_020

targetanswer 的元素之和。我们现在将搜索 arr 以查找总和为 1,226,020 的元素集合(answer 就是这样一个集合)。

require 'time'
t = Time.now
  #=> 2016-12-12 23:00:51 -0800 

a = sum_to_target(arr, target)
  #=> [3471, 1891, 4257, 2265, 832, 1060, 3961, 875, 614, 2308, 2240, 3286,
  #    ...
  #    3616, 4150, 3222, 3896, 631, 2806, 1932, 3244, 2430, 1443, 1452] 

注意a != answer(这并不奇怪)。

a.reduce(:+)
  #=> 1226020 

(Time.now-t).to_i
  #=> 60 seconds

对于最后一个示例,使用 Array#combination 的方法将不得不涉足尽可能多

(1..arr.size).reduce(0) { |t,i| t + arr.combination(i).size }.to_f
  #~> 1.07+301

组合。

说明

arr = [2,4,7,2]
target = 8

假设我们临时重新定义reconstruct 以返回传递给它的哈希值。

def reconstruct(h, target)
  h
end

然后我们得到以下内容:

h = sum_to_target(arr, target)
  #=> {2=>2, 6=>4, 4=>4, 9=>7, 13=>7, 11=>7, 7=>7, 8=>2}

h 定义如下。

给定一个非零整数数组arr 和一个数字n,如果nh 的键,则存在一个数组a,其中包含来自arr 的元素,在相同的顺序,这样a 的元素总和为na 的最后一个元素等于h[n]

诚然,这很拗口。

我们现在使用reconstruct(在“代码”部分中定义)来构造一个数组answer,该数组将包含来自arr(不包含重复元素)的元素,这些元素总和为target

reconstruct(h, target) #=> [2, 4, 2]

最初,reconstruct 初始化数组 answer,它将构建并返回:

answer = []

h 将始终包含一个等于目标 (8) 的键。作为h[8] #=> 2,我们得出answer的最后一个元素等于2,所以我们执行

answer.unshift(2) #=> [2]

现在的问题是从arr 中找到一个元素数组,总和为8 - 2 #=> 6。作为h[6] #=> 4,我们得出结论,answer 中我们刚刚添加的2 之前的元素是4

answer.unshift(4) #=> [4, 2]

我们现在需要更多 8-2-4 #=> 2 来总计 target。作为h[2] #=> 2,我们执行

answer.unshift(2) #=> [2, 4, 2]

由于8-2-4-2 #=> 0 我们已经完成,因此返回answer

请注意,4arr 中的最后一个 2 之前,第一个 2arr 中的 4 之前。 h 的构造方式确保了answer 的元素始终以这种方式排序。

现在考虑如何构造h。首先,

h = {}

作为arr[0] #=> 2,我们得出结论,仅使用arr 的第一个元素,我们可以得出的结论是:

h[2] = 2
h #=> {2=>2}

h 没有等于target (8) 的密钥,所以我们继续。现在考虑arr[1] #=> 4。仅使用arr 的前两个元素,我们可以得出以下结论:

h[2+4] = 4
h #=> {2=>2, 6=>4}

由于h 没有密钥4

h[4]   = 4
h #=> {2=>2, 6=>4, 4=>4}  

h 仍然没有与target (8) 相等的键,所以我们按下并检查arr[2] #=> 7。仅使用arr 的前三个元素,我们得出以下结论:

h[2+7] = 7
h[6+7] = 7
h[4+7] = 7
h #=> {2=>2, 6=>4, 4=>4, 9=>7, 13=>7, 11=>7}

因为h没有密钥7

h[7]   = 7
h #=> {2=>2, 6=>4, 4=>4, 9=>7, 13=>7, 11=>7, 7=>7}

我们在h 中添加了四个元素,但由于arr 仅包含正数,因此对具有键91311 的元素不感兴趣。

由于h 仍然没有等于target (8) 的键,我们检查arr 中的下一个元素:arr[3] #=> 2。仅使用arr 的前四个元素,我们得出以下结论:

h[4+2]  = 2
h[6+2]  = 2

我们在这里停下来,因为6+2 == target #=> true

h #=> {2=>2, 6=>2, 4=>4, 9=>7, 13=>7, 11=>7, 7=>7, 8=>2}

请注意,我们没有计算 h[2+2] = 2,因为 h 已经有一个密钥 4。此外,如果 arr 包含其他元素,我们仍然会在此时终止哈希的构造。

如果我们修改代码以利用 arr 仅包含正值这一事实,那么最终的哈希将是:

h #=> {2=>2, 6=>2, 4=>4, 7=>7, 8=>2}

如果这仍然不清楚,运行包含puts 语句的示例代码可能会有所帮助(例如,sum_to_target 中的v = arr[i] 行之后的puts "i=#{i}, h=#{h}, v=#{v}",等等)。

1 如果已知数组不包含负元素,则可以将行 unless h.key?(n+v) 更改为 unless h.key?(n+v) || (n+v > target)。 (这样做可以将 #4 的求解时间缩短 4 秒。)还可以计算 @all_positive = arr.all?(&:positive?),然后使该行以 @all_positive 为条件。

【讨论】:

  • 很好的解释。我一定会收藏它以供将来参考。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2021-11-16
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2015-08-17
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多