【问题标题】:given n, how to find the number of different ways to write n as the sum of 1, 3, 4 in ruby?给定n,如何在ruby中找到将n写为1、3、4之和的不同方式的数量?
【发布时间】:2023-04-11 05:46:01
【问题描述】:

问题:给定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

我尝试过置换方法,但是效率很低,有没有更高效的方法?

【问题讨论】:

  • 这闻起来像家庭作业。我是对的还是您正在尝试解决某种实际问题?
  • @mu 太短了,不,这是我在页面上看到的一个话题,我试过了,我用的是蛮力的方法,效率低,求帮助
  • @Cary Swoveland——(首先是确定与给定数字之和的 1、3 和 4 的不同数字。对于 n = 5,只有 3,我们可以写[[5,0,0], [2,1,0], [1,0,1]])--如何通过程序生成?
  • 是的,没错。我正在考虑如何有效地做到这一点。明天我可能有东西给你。也许其他人也会提出建议。

标签: ruby arrays algorithm data-structures


【解决方案1】:

使用带有查找表的动态编程(使用哈希实现,因为它使代码更简单):

nums=[1,3,4]
n=5

table={0=>1}
1.upto(n) { |i|
  table[i] = nums.map { |num| table[i-num].to_i }.reduce(:+)
}
table[n]
# => 6

注意:只需检查其他答案之一,我的答案是 n=500 的瞬时值。

【讨论】:

  • 非常令人印象深刻!当我第一次看到这个时,我认为它不可能是正确的,这么短,但它是。我承认我仍然对某一方面感到困惑,但让我再想一想。我假设.to_i 是为了处理nils(聪明)。 @sawa,一定要看看这个。
  • 我知道你是如何想出如此优雅的解决方案的:你是那些“地球人”中的一员。
  • 它更长,但我对其进行了一些重构。是的,.to_i 用于nils,而哈希是为了让我不必为负索引而烦恼。到底什么是“地球人”? :)
  • Karoly 和我的答案的关键是递归关系。这个更好用。顺便说一句,如果你以table = Hash.new{|h, k| h[k] = 0}开头,那么你可以摆脱to_i。我也想知道您是否可以使用injecteach_with_object 进行外部迭代。
  • @sawa:这是可能的,但首先您必须构建整个表,以便您可以对其进行迭代。我不知道有任何功能性循环结构可以让您参考之前(在循环中)created 列表/散列元素。不过话说回来,我对 ruby​​ 的经验不是很丰富……
【解决方案2】:
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

a:

[
  [1, 1, 1, 1, 1],
  [1, 1, 3],
  [1, 3, 1],
  [1, 4],
  [3, 1, 1],
  [4, 1]
]

a.length #=> 6

【讨论】:

  • 我期待在早上仔细看看这个。
  • 这是一个不错的 ruby​​ mind twister,虽然不想对其进行代码审查 :) 检查我的答案,它的效率要高得多。
【解决方案3】:

我认为这个问题应该分两步解决。

第一步

第一步是确定1s、3s 和4s 的不同数字之和为给定数字。对于n = 5,只有3个,我们可以这样写:

[[5,0,0], [2,1,0], [1,0,1]]

这3个元素分别被解释为“5个1、0个3、0个4”、“2个1、1个3、0个4”和“1个1、0个3、1个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 }

因为没有办法仅将 2s 相加等于 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]] }

这表明,例如:

  • 只有一种方法可以使用 1 和 3 相加为 1:1 =&gt; [1,0],表示一个 1 和零个 3。
  • 有两种方法求和为 4:4 =&gt; [[4,0], [1,1]],表示四个 1 和零个 3 或一个 1 和一个 3。

同理,当1、3、4都可以使用时,我们得到hash:

h2 = { 5 => [[5,0,0], [2,1,0], [1,0,1]] }

由于此哈希对应于所有三个数字 1、3 和 4 的使用,因此我们只关心总和为 5 的组合。

在构造h2时,我们可以使用零个4或一个4。如果我们使用零个4,我们将使用一个1和3的总和为5。我们从h1看到有两种组合:

5 => [[5,0], [2,1]]

对于h2,我们将这些写为:

[[5,0,0], [2,1,0]]

如果使用一个 4,则使用 1s 和 3s 总计 5 - 1*4 = 1。从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]]  

除此之外:由于我选择表示哈希h1h2 的哈希形式,实际上将h0 表示为更方便:

h0 = { 0 => [[0]], 1 => [[1]],..., 5 => [[5]] }  

应该很明显,这种顺序方法可以用于任何要对组合求和的整数集合。

第 2 步

在步骤 1 中产生的每个数组[n1, n3, n4] 的不同排列的数量等于:

(n1+n3+n4)!/(n1!n3!n4!)

请注意,如果n 之一为零,则这些将是二项式系数。事实上,这些是来自multinomial distribution 的系数,它是二项分布的推广。道理很简单。分子给出所有数字的排列数。对于每个不同的排列,n1 1 可以以n1! 方式置换,因此我们除以n1!n3n4 也一样

求和到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  

还有一个助手:

class Fixnum
  def factorial
    return 1 if self < 2
    (1..self).reduce(:*)
  end
end

示例

count_combos([1,3,4], 5)     #=> 6
count_combos([1,3,4], 6)     #=> 9
count_combos([1,3,4], 9)     #=> 40
count_combos([1,3,4], 15)    #=> 714
count_combos([1,3,4], 30)    #=> 974169
count_combos([1,3,4], 50)    #=> 14736260449
count_combos([2,3,4], 50)    #=> 72581632   
count_combos([2,3,4,6], 30)  #=> 82521
count_combos([1,3,4], 500)   #1632395546095013745514524935957247\
  00017620846265794375806005112440749890967784788181321124006922685358001

(为了显示的目的,我将示例(一个长数字)的结果分成两部分。)

count_combos([1,3,4], 500) 计算大约需要 2 秒;其他的基本上是瞬时的。

@sawa 的方法和我的方法在 6 到 9 之间对n 给出了相同的结果,所以我相信它们都是正确的。使用n 时,sawa 的求解时间增加得比我的要快得多,因为他正在计算然后计算所有排列。

编辑:@Karole,他刚刚发布了一个答案,我的所有测试(包括最后一个!)都得到了相同的结果。我更喜欢哪个答案?嗯。让我考虑一下。)

【讨论】:

  • 我才意识到这篇文章。我知道你会用这个做点什么。
  • 你确定你的结果是正确的吗?检查我的帖子。
  • Karoly,我重新运行了那个测试,得到了和你一样的 105 位数字! (见编辑。)
【解决方案4】:

我不知道 ruby​​,所以我用 C++ 编写它 比如说你的例子n = 5。 使用动态规划集

int D[n],n;
cin>>n;
D[0]=1;
D[1]=1;
D[2]=1;
D[3]=2; 
for(i = 4; i <= n; i++)
    D[i] = D[i-1] + D[i-3] + D[i-4];
cout<<D[i];

【讨论】:

  • 你在什么基础上初始化了 D[0]=1;
猜你喜欢
  • 2017-04-27
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多