【问题标题】:Given an integer n, return the number of ways it can be represented as a sum of 1s and 2s给定一个整数 n,返回它可以表示为 1 和 2 之和的方式数
【发布时间】:2016-11-28 01:31:45
【问题描述】:

例如:

5 = 1+1+1+1+1

5 = 1+1+1+2

5 = 1+1+2+1

5 = 1+2+1+1

5 = 2+1+1+1


5 = 1+2+2

5 = 2+2+1

5 = 2+1+2

任何人都可以提供有关如何完成此操作的伪代码的提示。 老实说,甚至不知道如何开始。 这看起来像一个指数问题,它可以在线性时间内完成吗?

谢谢。

【问题讨论】:

  • 看起来可能是第 (n+1) 个 Fibonacci number?
  • 解可能是 N 的指数,所以我猜不可能有线性方法。
  • 因为它是斐波那契循环,所以可以在对数时间内完成。
  • @CaptainGiraffe 如果问题是列举方式,那你是对的。但它不是,它要求确定有多少种方法。这可以在不枚举它们的情况下完成。
  • @pjs 好电话。

标签: c++ algorithm


【解决方案1】:

在您提供的示例中,加数的顺序很重要。 (请参阅示例中的最后两行)。考虑到这一点,答案似乎与斐波那契数有关。让我们 F(n) 成为 n 可以写成 1 和 2 的方式。然后最后添加的是 1 或 2。所以F(n) = F(n-1) + F(n-2)。这些是初始值:

F(1) = 1 (1 = 1)
F(2) = 2 (2 = 1 + 1, 2 = 2)

【讨论】:

  • 感谢您的输入,如果我按照这个逻辑,我可以得到 f(3) = f(2) + f(1),如果我手动检查,这是正确的 3 = 2+ 1 , 3= 1+2 , 3 =1 + 1 +1 ... 现在我不明白的是 f(4) = f(3) + f(1) 所以 f(3) 我们从前面知道f(3)=3 和 f(1) =1 所以我认为答案是 4,但是手动写出来给了我 5 种不同的方法。 4 = 1 + 1 + 1 +1 4 = 1 + 1 + 2 4 = 1 + 2 +1 4 = 2 + 1 +1 4 = 2 + 2 我哪里弄错了?
  • @Phil15 f(4) = f(3) + f(2) = 3 + 2 = 5
【解决方案2】:

这实际上是第 (n+1) 个斐波那契数。原因如下:

我们称 f(n) 为表示 n 的方法数。如果你有 n,那么你可以将它表示为 (n-1)+1 或 (n-2)+2。因此,表示它的方法是表示它的方法的数量是 f(n-1) + f(n-2)。这与斐波那契数列相同。此外,我们看到如果 n=1 则我们有 1 种方式,如果 n=2 则我们有 2 种方式。因此,第 (n+1) 个斐波那契数就是您的答案。有一些算法可以非常快速地计算出巨大的斐波那契数。

【讨论】:

  • 感谢您的回答,我现在可以看到它是 fib(n+1) 但您是如何弄清楚的,如果问题需要 1 或 2 或 3 个步骤怎么办。所以对于 f(3) -> fib(3+1) = fib(2+1) + fib(1 + 1) + fib(0 +1) .. 但我不明白为什么斐波那契适用于此?
  • 您的意思是将其表示为 1、2 或 3 的总和?那么问题将更具挑战性。您仍然应该能够使用相当直接的动态编程解决方案来做到这一点,但它不会像计算斐波那契数那样“好”。
【解决方案3】:

排列

如果我们想知道在不重复的情况下在某个大小 n 的集合中有多少可能的排序(即,从可用池中删除所选元素),factorial n(或 n!)给出了答案:

double factorial(int n)
{
    if (n <= 0)
        return 1;
    else
        return n * factorial(n - 1);
}

注意:这也有一个迭代解,甚至可以使用 gamma 函数来近似:

std::round(std::tgamma(n + 1)); // where n >= 0

问题集从所有 1s 开始。每次更改集合时,两个 1 被一个 2 替换。我们想找出 k 个项目(2s)可以排列成一组大小 n 的方式的数量。我们可以通过计算查询可能的排列数:

double permutation(int n, int k)
{
    return factorial(n) / factorial(n - k);
}

但是,这并不是我们想要的结果。问题是,排列考虑排序,例如,序列 2,2,2 将被视为六个不同的变体。

组合

这些本质上是忽略排序的排列。由于顺序不再重要,因此许多排列是多余的。通过计算 k! 可以找到每个排列的冗余。将排列数除以该值得出组合数:

注意:这称为binomial coefficient,应该读作“n选择k。”

double combination(int n, int k)
{
    return permutation(n, k) / factorial(k);
}

int solve(int n)
{
    double result = 0;

    if (n > 0) {
        for ( int k = 0; k <= n; k += 1, n -= 1 )
            result += combination(n, k);
    }
    return std::round(result);
}

这是一个通用的解决方案。例如,如果问题是要找出整数可以表示为 1s3s 之和的方式数,我们只需要调整每次迭代时设置的大小 (n-2)。

斐波那契数列

使用斐波那契数的解决方案起作用的原因与它们与二项式系数的关系有关。二项式系数可以排列成Pascal's triangle,当存储为下三角矩阵时,可以使用 nk 作为行/列索引来访问等于combination(n,k)的元素。

nk 的模式随着它们在 solve 的生命周期内的变化,当被视为 2- 上的坐标时绘制对角线D 网格。沿着帕斯卡三角形的对角线求和的结果是一个斐波那契数。如果模式发生变化(例如,当找到 1s3s 的总和时),情况将不再如此,此解决方案将失败。

有趣的是,斐波那契数可以在恒定时间内计算。这意味着我们可以通过找到第(n+1)个斐波那契数来在恒定时间内解决这个问题。

int fibonacci(int n)
{
    constexpr double SQRT_5 = std::sqrt(5.0);
    constexpr double GOLDEN_RATIO = (SQRT_5 + 1.0) / 2.0;

    return std::round(std::pow(GOLDEN_RATIO, n) / SQRT_5);
}

int solve(int n)
{
    if (n > 0)
        return fibonacci(n + 1);
    return 0;
}

最后一点,factorialfibonacci 函数生成的数字可能非常大。因此,如果 n 很大,则可能需要大型数学库。

【讨论】:

    【解决方案4】:

    这是使用回溯的代码,可以解决您的问题。在每个步骤中,在记住迄今为止用于求和的数字的同时(在此处使用向量),首先制作它们的副本,首先从 n 中减去 1 并将其添加到副本中,然后使用 n-1 和副本添加 1 的向量并在 n==0 时打印。然后返回并重复 2,这本质上是回溯。

    #include <stdio.h>
    #include <vector>
    #include <iostream>
    using namespace std;
    int n;
    void print(vector<int> vect){
        cout << n <<" = ";
        for(int i=0;i<vect.size(); ++i){
           if(i>0)
               cout <<"+" <<vect[i];
           else cout << vect[i];
        }
        cout << endl;
    }
    
    void gen(int n, vector<int> vect){
       if(!n)
          print(vect);
       else{
          for(int i=1;i<=2;++i){
              if(n-i>=0){
                  std::vector<int> vect2(vect);
                  vect2.push_back(i);
                  gen(n-i,vect2);
              }
          }
       }
    }
    
    int main(){
       scanf("%d",&n);
       vector<int> vect;
       gen(n,vect);
    }
    

    【讨论】:

    • OP 询问了 number 种方式,而不是如何枚举它们。
    • 我的错……我看了他的例子,他要求提供伪代码,然后谈到问题是指数级的,所以我认为他可能需要枚举它们。
    【解决方案5】:

    这个问题可以很容易地形象化如下:

    考虑一只青蛙,它出现在楼梯前。它需要到达n-th 楼梯,但他一次只能在楼梯上跳一两步。找出他可以到达n-th 楼梯的方式数?

    T(n) 表示到达n-th 楼梯的路数。

    所以,T(1) = 1T(2) = 2(2 个一级跳转或 1 个二级跳转,所以 2 种方式)

    为了到达n-th 楼梯,我们已经知道到达(n-1)th 楼梯和(n-2)th 楼梯的方式。

    所以,一旦从(n-1)th 楼梯跳1 步或从(n-2)th 跳2 步,就可以简单地到达n-th 楼梯...

    因此,T(n) = T(n-1) + T(n-2)

    希望对你有帮助!!!

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2013-11-08
      • 2021-05-10
      • 1970-01-01
      • 2015-10-30
      • 2021-09-14
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多