给定n,如何在ruby?中找到将n写为1,3,4之和的不同方法的数目;
问题:给定n,找出将n写成1,3,4之和的不同方法的数目 例:对于n=5,答案是6给定n,如何在ruby?中找到将n写为1,3,4之和的不同方法的数目;,ruby,arrays,algorithm,data-structures,Ruby,Arrays,Algorithm,Data Structures,问题:给定n,找出将n写成1,3,4之和的不同方法的数目 例:对于n=5,答案是6 5=1+1+1+1+1 5=1+1+3 5=1+3+1 5=3+1+1 5=1+4 5=4+1 我尝试过排列法,但它的效率很低,有没有更有效的方法呢?我认为这个问题应该分两步解决 def add_next sum, a1, a2 residue = a1.inject(sum, :-) residue.zero? ? [a1] : a2.reject{|x| residue < x}.
5=1+1+1+1+1
5=1+1+3
5=1+3+1
5=3+1+1
5=1+4
5=4+1
我尝试过排列法,但它的效率很低,有没有更有效的方法呢?我认为这个问题应该分两步解决
def add_next sum, a1, a2
residue = a1.inject(sum, :-)
residue.zero? ? [a1] : a2.reject{|x| residue < x}.map{|x| a1 + [x]}
end
a = [[]]
until a == (b = a.flat_map{|a| add_next(5, a, [1, 3, 4])})
a = b
end
第一步
第一步是确定与给定数字相加的1
s、3
s和4
s的不同数目。对于n=5
,我们只能写3个:
[[5,0,0], [2,1,0], [1,0,1]]
这三个元素分别被解释为“五个1、零3和零4”、“两个1、一个3和零4”以及“一个1、零3和一个4”
为了有效地计算这些组合,我首先只使用1来计算可能的组合,即0到5之间的每个数字的总和(这当然是微不足道的)。这些值保存在散列中,散列的键是求和数,值是求和到键的值所需的1的数目:
h0 = { 0 => 0, 1 => 1, 2 => 2, 3 => 3, 4 => 4, 5 => 5 }
(如果第一个数字是2,而不是1,则为:
h0 = { 0 => 0, 2 => 1, 4 => 2 }
因为无法将2相加为等于1或3。)
接下来,我们考虑使用1和3来将每个值与0和5相加。使用的3的数量只有两种选择,零或一。这就产生了散列:
h1 = { 0 => [[0,0]], 1 => [[1,0]], 2 => [[2,0]], 3 => [[3,0], [0,1]],
4 => [[4,0], [1,1]], 5 => [[5,0], [2,1]] }
h2 = { 5 => [[5,0,0], [2,1,0], [1,0,1]] }
例如,这表明:
- 只有一种方法可以使用1和3求和为1:
,表示1和01=>[1,0]
- 求和有两种方式:
,意思是四个1和零个3或一个1和一个34=>[[4,0],[1,1]]
h1 = { 0 => [[0,0]], 1 => [[1,0]], 2 => [[2,0]], 3 => [[3,0], [0,1]],
4 => [[4,0], [1,1]], 5 => [[5,0], [2,1]] }
h2 = { 5 => [[5,0,0], [2,1,0], [1,0,1]] }
由于该散列对应于所有三个数字1、3和4的使用,因此我们只关注总和为5的组合
在构造h2时,我们可以使用零4s或一个4。如果我们使用零4s,我们将使用一个1s和3,总和为5。我们从h1
中看到有两种组合:
5 => [[5,0], [2,1]]
对于h2
,我们将其写成:
[[5,0,0], [2,1,0]]
如果使用一个4,则使用总计为5-1*4=1的1和3。从h1
中,我们看到只有一种组合:
1 => [[1,0]]
对于h2
我们写为
[[1,0,1]]
所以
h2
中键5
的值为:
[[5,0,0], [2,1,0]] + [[1,0,1]] = [[5,0,0], [2,1,0]], [1,0,1]]
旁白:由于我选择了散列的形式来表示散列h1
和h2
,因此将h0
表示为:
h0 = { 0 => [[0]], 1 => [[1]],..., 5 => [[5]] }
显然,这种顺序方法可以用于任何组合要求和的整数集合
步骤2
在步骤1中生成的每个阵列的不同排列数[n1,n3,n4]
等于:
(n1+n3+n4)!/(n1!n3!n4!)
请注意,如果n
中的一个为零,则这些将是二项式系数。如果事实上,这些是来自的系数,这是二项式分布的推广。理由很简单。分子给出了所有数字的排列数。n1
1s可以排列n1代码>每个不同排列的方式,因此我们除以n1代码>。对于n3
和n4
对于求和到5
的示例,有:
5/5! = 1
对[5,0,0]
(2+1)/(2!1!)=3
针对[2,1,0]
和
(1+1)/(1!1!)=2
针对[1,0,1]
的不同安排,总共:
1+3+2=6
数字5的不同排列
代码
def count_combos(arr, n)
a = make_combos(arr,n)
a.reduce(0) { |tot,b| tot + multinomial(b) }
end
def make_combos(arr, n)
arr.size.times.each_with_object([]) do |i,a|
val = arr[i]
if i.zero?
a[0] = (0..n).each_with_object({}) { |t,h|
h[t] = [[t/val]] if (t%val).zero? }
else
first = (i==arr.size-1) ? n : 0
a[i] = (first..n).each_with_object({}) do |t,h|
combos = (0..t/val).each_with_object([]) do |p,b|
prev = a[i-1][t-p*val]
prev.map { |pr| b << (pr +[p]) } if prev
end
h[t] = combos unless combos.empty?
end
end
end.last[n]
end
def multinomial(arr)
(arr.reduce(:+)).factorial/(arr.reduce(1) { |tot,n|
tot * n.factorial })
end
(为了便于显示,我将示例的结果(一个长数字)分成两部分。)
count\u组合([1,3,4],500)
计算大约需要2秒;其他的基本上是瞬间的
@sawa的方法和我的方法对6到9之间的n
给出了相同的结果,因此我相信它们都是正确的。sawa的求解时间随着n
的增加要比我的快得多,因为他在计算然后计算所有的置换
编辑:@Karole,他刚刚发布了一个答案,我所有的测试(包括最后一个)都得到了相同的结果。我更喜欢哪个答案?嗯,让我想想。)我认为这个问题应该分两步解决
第一步
第一步是确定与给定数字相加的1
s、3
s和4
s的不同数目。对于n=5
,我们只能写3个:
[[5,0,0], [2,1,0], [1,0,1]]
这三个元素分别被解释为“五个1、零3和零4”、“两个1、一个3和零4”以及“一个1、零3和一个4”
为了有效地计算这些组合,我首先只使用1来计算可能的组合,即0到5之间的每个数字的总和(这当然是微不足道的)。这些值保存在散列中,散列的键是求和数,值是求和到键的值所需的1的数目:
h0 = { 0 => 0, 1 => 1, 2 => 2, 3 => 3, 4 => 4, 5 => 5 }
(如果第一个数字是2,而不是1,则为:
h0 = { 0 => 0, 2 => 1, 4 => 2 }
因为无法将2相加为等于1或3。)
接下来,我们考虑使用1和3来将每个值与0和5相加。使用的3的数量只有两种选择,零或一。这就产生了散列:
h1 = { 0 => [[0,0]], 1 => [[1,0]], 2 => [[2,0]], 3 => [[3,0], [0,1]],
4 => [[4,0], [1,1]], 5 => [[5,0], [2,1]] }
h2 = { 5 => [[5,0,0], [2,1,0], [1,0,1]] }
例如,这表明:
- 只有一种方法可以使用1和3求和为1:
1=>[1,0]
,表示1和0
- 求和有两种方式:
4=>[[4,0],[1,1]]
,意思是四个1和零个3或一个1和一个3
类似地,当1、3和4都可以使用时,我们得到散列:
h1 = { 0 => [[0,0]], 1 => [[1,0]], 2 => [[2,0]], 3 => [[3,0], [0,1]],
4 => [[4,0], [1,1]], 5 => [[5,0], [2,1]] }
h2 = { 5 => [[5,0,0], [2,1,0], [1,0,1]] }
由于该散列对应于所有三个数字1、3和4的使用,因此我们只关注总和为5的组合
在建设中<