如何使用递归找到数组的所有子集总和为零? (在Ruby中)

时间:2014-05-19 03:31:10

标签: ruby recursion subset-sum

我是编程新手,也是Ruby新手。我在业余时间处理这个问题,因为我刚接触ruby并且很难让我的代码遍历每个创建的子集。

这是我的代码:

#Given a set of integers, and a value sum, i.e. value sum of 0
#determine if there is a subset of the given set with sum equal to given sum.

class Array
    def SubSetSumtoZero
        if self.collect{|sum,x| sum + x  == 0}
            detected = self.select {|sum,x| sum + x == 0}
            puts "\r\n #{detected} Sums to Zero \r\n"
        else self.collect{|sum,x| sum + x  -= 0}
            notdetected = self.select {|sum, x| sum + x -= 0}
            puts "\r\n#{notdetected} Does not sum to Zero\r\n"
        end
    end
end

originalSet = [-9, -7, -2, 2, 7, 9]
arr = []

for i in 2..(originalSet.length) do
    arr = arr + originalSet.combination(i).to_a
    arr.SubSetSumtoZero
end

以下是我的结果:

[[-9, 9], [-7, 7], [-2, 2]] Sums to Zero 

 [[-9, 9], [-7, 7], [-2, 2], [-7, 7, 9], [-2, 2, 7], [-2, 2, 9]] Sums to Zero 

 [[-9, 9], [-7, 7], [-2, 2], [-7, 7, 9], [-2, 2, 7], [-2, 2, 9], [-2, 2, 7, 9]] Sums to Zero 

 [[-9, 9], [-7, 7], [-2, 2], [-7, 7, 9], [-2, 2, 7], [-2, 2, 9], [-2, 2, 7, 9]] Sums to Zero 

 [[-9, 9], [-7, 7], [-2, 2], [-7, 7, 9], [-2, 2, 7], [-2, 2, 9], [-2, 2, 7, 9]] Sums to Zero 
[Finished in 0.1s]

我知道在某些时候整个阵列总和为零。任何想法为什么会发生这种情况?

3 个答案:

答案 0 :(得分:3)

我知道你特意要求一个递归解决方案,但是由于@hjing提供了一个解决方案,我想通过使用Ruby&的强大内置方法向您展示如何以更紧凑和直接的方式回答您的问题。 #39; s Enumerable模块和Array类。

<强>代码

def find_it(array, tot)
  (1..array.size).each_with_object([]) { |n,arr|
    array.combination(n).each { |a| arr << a if a.reduce(:+) == tot } }
end

示例

find_it([-9, -7, -2, 2, 7, 9], 0) #=> [-9, 9]
  #=> [[-9, 9], [-7, 7], [-2, 2],
  #    [-9, 2, 7], [-7, -2, 9],
  #    [-9, -7, 7, 9], [-9, -2, 2, 9], [-7, -2, 2, 7],
  #    [-9, -7, -2, 2, 7, 9]]

<强>解释

array = [-9, -7, -2, 2, 7, 9]
tot   = 0

r = 1..array.size             #=> 1..6 (Range object)

此范围的每个值都是要考虑的子集的大小。我们将首先检查大小为1的子集,然后检查大小为2的子集,依此类推,直到大小为6的子集,其中只有一个(array)。

enum1 = r.each_with_object([]) # => #<Enumerator: 1..6:each_with_object([])>

要查看枚举器enum1将传递给其块的值,我们可以将其转换为数组:

enum1.to_a  #=> [[1, []], [2, []], [3, []], [4, []], [5, []], [6, []]]

Enumerable#each_with_object创建一个最初为空的数组,由块变量arr表示。第一个值enum传递给它的块是数组[0, []],导致块变量按如下方式分配:

n   => 1
arr => []

enum2 = array.combination(n) #=> array.combination(1)
  #=> #<Enumerator: [-9, -7, -2, 2, 7, 9]:combination(1)>

此处Array#combination生成array中一个元素的所有组合。因此,enum2会将以下元素传递给其块:

enum2.to_a
  #=> [[-9], [-7], [-2], [2], [7], [9]]

第一个元素enum2传递给它的块是[-9],导致bloc块变量被赋值如下:

a => [-9]

所以块表达式变为:

arr << a if a.reduce(:+) == tot #=> arr << [-9] if [-9].reduce(:+) == 0

Enumerable#reduce(aka,inject)带有参数:+(加法)只是对其接收器[-9]的元素求和,显然是{{1} }。由于-9-9 != 0未附加到[-9]。显然,唯一包含总和为零的单个元素的数组是arr,但该示例中不存在该数组。因此,[0]枚举其所有元素后,arr仍为空。

enum2现在将元素enum1传递给它的块,将块变量设置为:

[2, []]

导致:

n   => 2
arr => []

我们发现元素enum2 = array.combination(n) #=> array.combination(2) #=> #<Enumerator: [-9, -7, -2, 2, 7, 9]:combination(2)> enum2.to_a #=> [[-9, -7], [-9, -2], [-9, 2], [-9, 7], [-9, 9], [-7, -2], [-7, 2], # [-7, 7], [-7, 9], [-2, 2], [-2, 7], [-2, 9], [ 2, 7], [ 2, 9], [7, 9]] [-9, 9][-7, 7]各自为零,因此[-2, 2]变为:

arr

在枚举了两个元素的所有组合之后。考虑下三种组合,依此类推。

答案 1 :(得分:2)

一个简单的递归解决方案是编写一个计算数组powerset的函数。然后,选择powerset中满足所需谓词的所有元素。

示例实现:

def powerset(array)
  if array.empty?
    [[]]
  else
    first_elem, *rest_elems = array
    subsets = []
    powerset(rest_elems).each do |subset|
      subsets.push(subset)
      subsets.push(subset.clone.push(first_elem))
    end
    subsets
  end
end

def sums_to_zero?(array)
  array.reduce(0, :+) == 0
end

def subsets_that_sum_to_zero(array)
  powerset(array).select { |subset| sums_to_zero?(subset) }
end

original_set = [-9, -7, -2, 2, 7, 9]
subsets_that_sum_to_zero(original_set).each do |subset|
  puts "The subset #{subset} sums to zero!"
end

# The subset [] sums to zero!
# The subset [2, -2] sums to zero!
# The subset [7, -7] sums to zero!
# The subset [7, 2, -9] sums to zero!
# The subset [7, 2, -2, -7] sums to zero!
# The subset [9, -9] sums to zero!
# The subset [9, -2, -7] sums to zero!
# The subset [9, 2, -2, -9] sums to zero!
# The subset [9, 7, -7, -9] sums to zero!
# The subset [9, 7, 2, -2, -7, -9] sums to zero!

有关powerset算法的解释,请参阅wikipedia

答案 2 :(得分:0)

这是零和的直接递归解决方案:

def find_subsets(arr)
  return arr if arr.empty?
  result = (0...arr.length).flat_map do |i|
     find_subsets(arr[0...i] + arr[i+1..-1])
  end
  result << arr if arr.inject(:+) == 0
  result.uniq
end

它收集了从数组中删除单个元素的所有结果(这是递归),如果它符合要求(总和为零),则将数组本身添加到其中。

它不是最有效的解决方案,因为它可能重复来自多个路径的子阵列的计算(这就是为什么最后一行uniq的结果) - 但这是最​​简单的实现。