【问题标题】:Tricky Google interview question棘手的谷歌面试问题
【发布时间】:2011-07-27 05:27:47
【问题描述】:

我的一个朋友正在面试一份工作。其中一个面试问题让我思考,只是想要一些反馈。

有 2 个非负整数:i 和 j。给定以下等式,找到一个(最优)解来迭代 i 和 j,从而使输出排序。

2^i * 5^j

所以前几轮看起来像这样:

2^0 * 5^0 = 1
2^1 * 5^0 = 2
2^2 * 5^0 = 4
2^0 * 5^1 = 5
2^3 * 5^0 = 8
2^1 * 5^1 = 10
2^4 * 5^0 = 16
2^2 * 5^1 = 20
2^0 * 5^2 = 25

尽我所能,我看不到模式。你的想法?

【问题讨论】:

  • 就程序员时间而言,最佳算法是生成两个嵌套循环,然后排序。他们为什么会问这样的问题?
  • 您可以通过查看哪个数字更大来确定过渡点。 2^2 < 52^3 > 5 所以此时你增加了 j。我认为你可以在 O(n) 而不是 O(nlgn) 中产生输出。 @tom-zynch 两个嵌套循环是 O(n^2)。这个问题很有道理
  • 只有一个输出,所以最优解是 O(n)。在下面阅读我的解决方案
  • 之前显然已经解决了一个类似的问题:stackoverflow.com/questions/4600048/nth-ugly-number.
  • ... 并且 OP 可能应该已经选择了一个答案。毕竟,他已经有很多好人了。

标签: algorithm optimization hamming-numbers smooth-numbers


【解决方案1】:

Dijkstra 在“A Discipline of Programming”中得出了一个雄辩的解决方案。他将问题归咎于海明。 这是我对 Dijkstra 解决方案的实现。

int main()
{
    const int n = 20;       // Generate the first n numbers

    std::vector<int> v(n);
    v[0] = 1;

    int i2 = 0;             // Index for 2
    int i5 = 0;             // Index for 5

    int x2 = 2 * v[i2];     // Next two candidates
    int x5 = 5 * v[i5];

    for (int i = 1; i != n; ++i)
    {
        int m = std::min(x2, x5);
        std::cout << m << " ";
        v[i] = m;

        if (x2 == m)
        {
            ++i2;
            x2 = 2 * v[i2];
        }
        if (x5 == m)
        {
            ++i5;
            x5 = 5 * v[i5];
        }
    }

    std::cout << std::endl;
    return 0;
}

【讨论】:

  • 相关链接:en.wikipedia.org/wiki/Regular_number#Algorithms。顺便说一句,我认为这不是一个很好的面试问题。这是 Dijkstra 的一篇(手写论文),他提供并证明了解决这个问题的算法:cs.utexas.edu/users/EWD/ewd07xx/EWD792.PDF
  • 当目标是“迭代 i 和 j”时,您需要较少的存储容量,FIFO 就足够了。请参阅我的 Python 解决方案。
  • 当目标是“迭代i和j”时,就不是同一个问题了。
  • 这是一个非常好的实现,使用最少的内存。即使你只想要一个数字,它也是线性内存。
  • @ThomasAhle 不知道你有没有看到this,但它最后有代码可以单独计算第 n 个数字。就像例如billionth number.
【解决方案2】:

这是一种更精致的方法(比我之前的答案更精致,即):

想象数字被放置在一个矩阵中:

     0    1    2    3    4    5   -- this is i
----------------------------------------------
0|   1    2    4    8   16   32
1|   5   10   20   40   80  160
2|  25   50  100  200  400  800
3| 125  250  500 1000 2000 ...
4| 625 1250 2500 5000 ...
j on the vertical

您需要做的是“遍历”这个矩阵,从(0,0) 开始。您还需要跟踪您可能的下一步行动。当您从(0,0) 开始时,您只有两个选择:(0,1)(1,0):由于(0,1) 的值较小,您选择它。然后为您的下一个选择 (0,2)(1,0) 做同样的事情。到目前为止,您拥有以下列表:1, 2, 4。你的下一步是(1,0),因为那里的值小于(0,3)。但是,您现在有三个可供选择:(0,3)(1,1)(2,0)

您不需要矩阵来获取列表,但您确实需要跟踪所有选择(即当您达到 125+ 时,您将有 4 个选择)。

【讨论】:

  • 我投了赞成票,因为我的想法是一样的,但在一般情况下,这不会像 O(i^2 * j) 那样吗?您必须为输出的每个数字检查多个数字。
  • @Tom 您确实必须检查多个数字,但这还不错:当您输出 125 到 625 之间的数字时,您需要查看 4 个值。在 625 和 3025 之间,您查看 5 个值。所以真的,它是 j 每 1 个输出检查
  • +1:结合这个问题:stackoverflow.com/questions/5000836/search-algorithm 看起来我们有一个 O(n) 解决方案。
  • @Moron darn,我不想为那个算法支付 25 美元,但它看起来确实很有趣。
  • 实际上,j ~ n^0.5 表示序列中的第 n 个值,因为 n 值填充了 i x j 平面上的一个区域。所以这个算法是O(n^1.5) 时间,O(n^0.5) 空间。但是存在一个 linear 时间算法,其空间复杂度为 n^0.5,而来自下面答案的迷你堆算法是 O(n*log(n)) time 和相同的 n^0.5 空间。
【解决方案3】:

使用最小堆。

放置 1。

提取-最小。假设你得到 x。

将 2x 和 5x 推入堆中。

重复。

您可以存储 (i,j) 并使用自定义比较函数,而不是存储 x = 2^i * 5^j。

【讨论】:

  • 堆会给 lg n 的操作时间,这将复杂度推高到 n lg n。
  • @glow:是的,到目前为止,我没有看到任何 O(n) 解决方案,不过 :-)
  • @abel:那条评论很旧 :-) 似乎他从 (1,1) 到 (4,0) 也会遇到问题。但将其视为一个年轻的矩阵(参见 vlad 的答案)实际上确实允许 O(n) 时间算法。
  • @Moron:我认为该解决方案没有任何问题。前 30 个元素当然没有错,我现在刚刚检查过(这将涵盖 (1,1) -> (4,0) 的情况)。
  • @abel:是的,实际上并没有尝试运行它 :-) 也许也有一个简单的证明来证明它的正确性。 FWIW,它已经有了我的 +1。
【解决方案4】:

基于 FIFO 的解决方案需要更少的存储容量。 Python 代码。

F = [[1, 0, 0]]             # FIFO [value, i, j]
i2 = -1; n2 = n5 = None     # indices, nexts
for i in range(1000):       # print the first 1000
    last = F[-1][:]
    print "%3d. %21d = 2^%d * 5^%d" % tuple([i] + last)
    if n2 <= last: i2 += 1; n2 = F[i2][:]; n2[0] *= 2; n2[1] += 1
    if n5 <= last: i2 -= 1; n5 = F.pop(0); n5[0] *= 5; n5[2] += 1
    F.append(min(n2, n5))

输出:

  0.                     1 = 2^0 * 5^0
  1.                     2 = 2^1 * 5^0
  2.                     4 = 2^2 * 5^0
 ...
998. 100000000000000000000 = 2^20 * 5^20
999. 102400000000000000000 = 2^27 * 5^17

【讨论】:

    【解决方案5】:

    这在函数式语言中很容易做到O(n)2^i*5^j 的列表l 可以简单地定义为1,然后将2*l5*l 合并。这是它在 Haskell 中的样子:

    merge :: [Integer] -> [Integer] -> [Integer]
    merge (a:as) (b:bs)   
      | a < b   = a : (merge as (b:bs))
      | a == b  = a : (merge as bs)
      | b > a   = b : (merge (a:as) bs)
    
    xs :: [Integer]
    xs = 1 : merge (map(2*)xs) (map(5*)xs)
    

    merge 函数在恒定时间内为您提供了一个新值。 map 也是如此,l 也是如此。

    【讨论】:

    • 我认为'k'没有定义
    • 让我们只调用这个“合并”函数union,因为它正在删除重复项。 merge,作为mergesort 的一部分,必须保留来自其两个输入序列的重复项。有关相关内容,请参阅 Data.List.Ordered 包。
    • +1 表示Data.List.Ordered.union。这使它成为一行:xs = 1 : union (map (2*) xs) (map (5*) xs)
    • @GaBorgulya 是的,它包含列表的五倍 [1, 2, 4, 5,...] 所以它包含 5*4
    • @Phob 是的,这是Data.List.Ordered.union 函数。不要与Data.List.union 混淆。
    【解决方案6】:

    您必须跟踪它们的各个指数,以及它们的总和是多少

    所以你从f(0,0) --&gt; 1开始 现在你必须增加其中之一:

    f(1,0) = 2
    f(0,1) = 5
    

    所以我们知道 2 是下一个 - 我们也知道我们可以增加 i 的指数直到总和超过 5。

    你一直这样来回走动,直到达到你想要的回合数。

    【讨论】:

    • 是的。您为每一轮执行一次 O(1) 操作。有时你会提前完成一轮,但当你到达那轮时,你不必在那里进行,所以它会自行解决。
    • 如何从 (1,1) 到 (4,0)?请详细说明您的算法是什么。
    • 问题是,你不只是有两个增量的可能性 - 例如,你没有完成 f(*,2) 只是因为你发现了 f(a1,b+1)&gt;f(a2,b)。增量方法最终会在您已经输出的区域附近生成无限数量的对。
    • @user515430 提供的实现超出了我在午休时间所能做的,但这正是我想要达到的目标。
    【解决方案7】:

    使用动态编程,您可以在 O(n) 中做到这一点。基本事实是,i 和 j 的任何值都不能给我们 0,要得到 1,这两个值都必须为 0;

    TwoCount[1] = 0
    FiveCount[1] = 0
    
    // function returns two values i, and j
    FindIJ(x) {
        if (TwoCount[x / 2]) {
            i = TwoCount[x / 2] + 1
            j = FiveCount[x / 2]
        }
        else if (FiveCount[x / 5]) {
            i = TwoCount[x / 2]
            j = FiveCount[x / 5] + 1
        }
    }
    

    每当您调用此函数时,检查是否设置了 i 和 j,如果它们不为空,则填充 TwoCountFiveCount


    C++ 答案。抱歉编码风格不好,但我很着急:(

    #include <cstdlib>
    #include <iostream>
    #include <vector>
    
    int * TwoCount;
    int * FiveCount;
    
    using namespace std;
    
    void FindIJ(int x, int &i, int &j) {
            if (x % 2 == 0 && TwoCount[x / 2] > -1) {
                    cout << "There's a solution for " << (x/2) << endl;
                    i = TwoCount[x / 2] + 1;
                    j = FiveCount[x / 2];
            } else if (x % 5 == 0 && TwoCount[x / 5] > -1) {
                    cout << "There's a solution for " << (x/5) << endl;
                    i = TwoCount[x / 5];
                    j = FiveCount[x / 5] + 1;
            }    
    }
    
    int main() {
            TwoCount = new int[200];
            FiveCount = new int[200];
    
            for (int i = 0; i < 200; ++i) {
                    TwoCount[i] = -1;
                    FiveCount[i] = -1;
            }
    
            TwoCount[1] = 0;
            FiveCount[1] = 0;
    
            for (int output = 2; output < 100; output++) {
                    int i = -1;
                    int j = -1;
                    FindIJ(output, i, j);
                    if (i > -1 && j > -1) {
                            cout << "2^" << i << " * " << "5^" 
                                         << j << " = " << output << endl;
                            TwoCount[output] = i;
                            FiveCount[output] = j;
                    }
            }    
    }
    

    显然,您可以使用数组以外的数据结构来动态增加存储空间等。这只是证明它有效的草图。

    【讨论】:

    • 这看起来是一个有趣的答案,但我看不出它是如何工作的。你能补充更多细节吗?
    • 自己研究了一下,实在看不出来是怎么回事。假设整数除法,3 和 2 的结果完全相同。此外,如果 if 条件是非零测试,它永远不会起作用,因为没有非零条目。
    • 为所有反对者发布了 C++ 版本。 @David您的cmets是正确的,但是我的原始代码是伪代码,我在用脚本术语思考,所以不是整数除法并区分空条目和值0的条目
    • 此代码枚举所有自然数,因此,根据@ThomasAhle 对下面“迷失在阿拉巴马州”答案的评论,需要O(exp(sqrt(n))) 来生成序列的n 数字。 线性算法存在,例如由 ThomasAhle 给出。
    • 你是对的。在我的理解中,O(n) 的意思是 n 是最后一个值,而不是打印项目的数量,这是不正确的。我不知道函数式语言是如何工作的,也不知道合并是如何在恒定时间内工作的,但他的回答得到了我的支持
    【解决方案8】:

    为什么不尝试从另一个方向看这个。使用计数器根据原始公式测试可能的答案。抱歉,伪代码。

    for x = 1 to n
    {
      i=j=0
      y=x
      while ( y > 1 )
      {
        z=y
        if y divisible by 2 then increment i and divide y by 2
        if y divisible by 5 then increment j and divide y by 5
    
        if y=1 then print i,j & x  // done calculating for this x
    
        if z=y then exit while loop  // didn't divide anything this loop and this x is no good 
      }
    }
    

    【讨论】:

    • 这在大约O(4^sqrt(n)) 中运行,因为序列的nth 数大约是那个大小。
    【解决方案9】:

    This 是 OEIS 的相关条目。

    似乎可以通过生成前几个术语来获得有序序列,比如说

    1 2 4 5

    然后,从第二项开始,乘以 4 和 5 得到接下来的两项

    1 2 4 5 8 10

    1 2 4 5 8 10 16 20

    1 2 4 5 8 10 16 20 25

    等等……

    直觉上,这似乎是正确的,但当然缺少证据。

    【讨论】:

    • 错误 :( [1 2 4 5 8 10 16 20 25 32 40 50 64 80 100 125 128 160 200 250 256 320 400 500 625 ] 但是 500
    • @NateKerkhofs,生成了 512,但由于 512 小于已生成的 625,因此出现故障;该算法需要进一步的逻辑来整理输出 - 因此该算法并不像提出的那样简单,而且根本不是同一个算法。
    【解决方案10】:

    您知道 log_2(5)=2.32。由此我们注意到 2^2 5。

    现在看一个可能答案的矩阵:

    j/i  0   1   2   3   4   5
     0   1   2   4   8  16  32
     1   5  10  20  40  80 160 
     2  25  50 100 200 400 800
     3 125 250 500 ...
    

    现在,对于这个例子,按顺序选择数字。排序是:

    j/i  0   1   2   3   4   5
     0   1   2   3   5   7  10
     1   4   6   8  11  14  18
     2   9  12  15  19  23  27
     3  16  20  24...
    

    请注意,每一行在开始它的行后面开始 2 列。例如,i=0 j=1 直接出现在 i=2 j=0 之后。

    因此,我们可以从这个模式推导出的算法是(假设 j>i):

    int i = 2;
    int j = 5;
    int k;
    int m;
    
    int space = (int)(log((float)j)/log((float)i));
    for(k = 0; k < space*10; k++)
    {
        for(m = 0; m < 10; m++)
        {
            int newi = k-space*m;
            if(newi < 0)
                break;
            else if(newi > 10)
                continue;
            int result = pow((float)i,newi) * pow((float)j,m);
            printf("%d^%d * %d^%d = %d\n", i, newi, j, m, result);
        }
    }   
    

    注意:此处的代码将 i 和 j 的指数值限制为小于 10。您可以轻松扩展此算法以适应任何其他任意范围。

    注意:对于前 n 个答案,此算法的运行时间为 O(n)。

    注意:此算法的空间复杂度为 O(1)

    【讨论】:

    • 您写了“每一行在开始它的行后面开始 2 列”。但是 2^9=512 和 5^4=625,所以这不适用于第 4 行。
    • @user678105 你是对的。此代码不起作用。对不起大家。由于日志的四舍五入以及我认为它无关紧要的假设,此代码不起作用。
    • 以下是解决此问题的方法。在充满积分系数点的 (x,y) 平面上,画一条从 (0,1) 到 (log2(5),0) 的线。 (0,0) 在左上角。 X轴向右,Y轴向下。现在从垂直于第一条线的 (0,0) 原点画一条线。现在沿着第二条线滑动第一条线,离原点越来越远,并在它们交叉时收集整数坐标点。对于 {2,3,5} 生成的序列,它将是一个在 (i,j,k) 空间中移动的平面。如果您可以将这个想法转化为代码,请给我一个呐喊。 :)
    【解决方案11】:

    我的实现基于以下想法:

    • 使用两个队列 Q2 和 Q5,均以 1 初始化。我们将保持这两个队列的排序顺序。
    • 在每一步,从 Q2 或 Q5 中取出最小数字元素 MIN 并打印它。如果 Q2 和 Q5 都具有相同的元素 - 将两者都删除。打印此号码。这基本上是两个排序数组的合并 - 在每一步中选择最小的元素并前进。
    • 将 MIN*2 排入 Q2 并将 MIN*5 排入 Q5。此更改不会破坏正在排序的 Q2/Q5 的不变量,因为 MIN 高于之前的 MIN 数字。

    例子:

    Start with 1 and 1 (to handle i=0;j=0 case):
      Q2: 1
      Q5: 1
    Dequeue 1, print it and enqueue 1*2 and 1*5:
      Q2: 2
      Q5: 5
    Pick 2 and add 2*2 and 2*5:
      Q2: 4
      Q5: 5 10
    Pick 4 and add 4*2 and 4*5:
      Q2: 8
      Q5: 5 10 20
    ....
    

    Java 代码:

    public void printNumbers(int n) {
        Queue<Integer> q2 = new LinkedList<Integer>();
        Queue<Integer> q5 = new LinkedList<Integer>();
        q2.add(1);
        q5.add(1);
        for (int i = 0; i < n; i++) {
            int a = q2.peek();
            int b = q5.peek();
            int min = Math.min(a, b);
            System.out.println(min);
            if (min == a) {
                q2.remove();
            }
            if (min == b) {
                q5.remove();
            }
            q2.add(min * 2);
            q5.add(min * 5);
        }
    }
    

    【讨论】:

      【解决方案12】:

      计算结果并将它们与ij 的值一起放入排序列表中

      【讨论】:

      • 这可能会在序列的后期给您带来漏洞。例如。您将拥有2^n*5^n,但不会拥有较小的2^(n+1)*5^(n-1)
      • @Thomas 我不确定我是否遵循您的逻辑。如果你计算一个,为什么你不计算另一个?
      • @vlad 你需要限制ij 的数量,不是吗?否则,您将永远无法进入排序状态,因此您将永远不会返回单个值。但是对于您选择的任何限制n,您的列表将有缺陷。
      • @Thomas 你的论点仍然没有意义。 OP 从未指定结束他的结果列表。如果他这样做了,您可以找到最大值 ij
      • @vlad 当我阅读您的答案时,您首先计算“结果”/2^i*5^j 值,然后对它们进行排序。如果您没有有限数量的“结果”,您将如何进入排序步骤?
      【解决方案13】:

      由 Edsger Dijkstra (http://www.cs.utexas.edu/users/EWD/ewd07xx/EWD792.PDF) 的 user515430 实现的算法可能是你能得到的最快的。我将每个2^i * 5^j 形式的号码称为“特殊号码”。现在 vlads 的答案将是 O(i*j),但使用双重算法,一个生成特殊数字 O(i*j),另一个对它们进行排序(根据链接的文章还有 O(i*j)

      但是让我们检查一下 Dijkstra 的算法(见下文)。在这种情况下,n 是我们生成的特殊数字的数量,因此等于i*j。我们循环一次,1 -&gt; n,在每个循环中我们执行一个恒定的动作。所以这个算法也是O(i*j)。还有一个非常快的常数。

      我在 C++ 中使用 GMP(C++ 包装器)实现,以及对 boost::lexical_cast 的依赖,尽管可以轻松删除(我很懒,谁不使用 Boost?)。使用g++ -O3 test.cpp -lgmpxx -o test 编译。在 Q6600 Ubuntu 10.10 上,time ./test 1000000 提供 1145ms

      #include <iostream>
      #include <boost/lexical_cast.hpp>
      #include <gmpxx.h>
      
      int main(int argc, char *argv[]) {
          mpz_class m, x2, x5, *array, r;
          long n, i, i2, i5;
      
          if (argc < 2) return 1;
      
          n = boost::lexical_cast<long>(argv[1]);
      
          array = new mpz_class[n];
          array[0] = 1;
      
          x2 = 2;
          x5 = 5;
          i2 = i5 = 0;
      
          for (i = 1; i != n; ++i) {
              m = std::min(x2, x5);
      
              array[i] = m;
      
              if (x2 == m) {
                  ++i2;
                  x2 = 2 * array[i2];
              }
      
              if (x5 == m) {
                  ++i5;
                  x5 = 5 * array[i5];
              }
          }
      
          delete [] array;
          std::cout << m << std::endl;
      
          return 0;
      }
      

      【讨论】:

        【解决方案14】:

        如果您绘制一个以 i 为行、j 为列的矩阵,您可以看到该模式。从 i = 0 开始,然后向上移动 2 行并向右 1 列遍历矩阵,直到到达矩阵的顶部 (j >= 0)。然后去 i + 1 等...

        所以对于 i = 7,你会这样旅行:

        7, 0 -> 5, 1 -> 3, 2 -> 1, 3
        

        对于 i = 8:

        8, 0 -> 6, 1 -> 4, 2 -> 2, 3 -> 0, 4
        

        这是在 Java 中直到 i = 9。它打印矩阵位置 (i, j) 和值。

        for(int k = 0; k < 10; k++) {
        
            int j = 0;
        
            for(int i = k; i >= 0; i -= 2) {
        
                int value = (int)(Math.pow(2, i) * Math.pow(5, j));
                System.out.println(i + ", " + j + " -> " + value);
                j++;
            }
        }
        

        【讨论】:

          【解决方案15】:

          我的直觉

          如果我取初始值为 1,其中 i=0,j=0,那么 我可以将下一个数字创建为 (2^1)(5^0), (2^2)(5^0), (2^0)*(5^1), ... 即 2,4,5 ..

          假设我的号码是 x。然后我可以通过以下方式创建下一个数字:

          • x * 2
          • x * 4
          • x * 5

          解释

          Since new numbers can only be the product with 2 or 5.
          But 4 (pow(2,2)) is smaller than 5, and also we have to generate 
          Numbers in sorted order.Therefore we will consider next numbers
          be multiplied with 2,4,5.
          Why we have taken x*4 ? Reason is to pace up i, such that it should not 
          be greater than pace of j(which is 5 to power). It means I will 
          multiply my number by 2, then by 4(since 4 < 5), and then by 5 
          to get the next three numbers in sorted order.
          

          测试运行

          We need to take an Array-list of Integers, let say Arr.
          
          Also put our elements in Array List<Integers> Arr.
          Initially it contains Arr : [1]
          
          • 让我们从 x = 1 开始。

            接下来的三个数字是1*2, 1*4, 1*5 [2,4,5]; Arr[1,2,4,5]

          • 现在 x = 2

            接下来的三个数字是 [4,8,10] {由于 4 已经出现,我们将 忽略它} [8,10]; Arr[1,2,4,5,8,10]

          • 现在 x =4

            接下来的三个数字 [8,16,20] {8 已经发生忽略它} [16,20] Arr[1,2,4,5,8,10,16,20]

          • x = 5

            接下来的三个数字 [10,20,25] {10,20} 已经添加了 [25] Arr[1,2,4,5,8,10,16,20,25]

          终止条件

           Terminating condition when Arr last number becomes greater 
           than (5^m1 * 2^m2), where m1,m2 are given by user.
          

          分析

           Time Complexity : O(K) : where k is numbers possible between i,j=0 to 
           i=m1,j=m2.
           Space Complexity : O(K)
          

          【讨论】:

            【解决方案16】:

            只是好奇下周会发生什么,发现了这个问题。

            我认为,这个想法是 2^i 不会像 5^j 那样大步增加。所以只要下一个 j 步不会变大,就增加 i。

            C++ 中的示例(Qt 是可选的):

            QFile f("out.txt"); //use output method of your choice here
            f.open(QIODevice::WriteOnly);
            QTextStream ts(&f);
            
            int i=0;
            int res=0;
            for( int j=0; j<10; ++j )
            {
                int powI = std::pow(2.0,i );
                int powJ = std::pow(5.0,j );
                while ( powI <= powJ  ) 
                {
                    res = powI * powJ;
                    if ( res<0 ) 
                        break; //integer range overflow
            
                    ts<<i<<"\t"<<j<<"\t"<<res<<"\n";
                    ++i;
                    powI = std::pow(2.0,i );
            
                }
            }
            

            输出:

            i   j   2^i * 5^j
            0   0   1
            1   1   10
            2   1   20
            3   2   200
            4   2   400
            5   3   4000
            6   3   8000
            7   4   80000
            8   4   160000
            9   4   320000
            10  5   3200000
            11  5   6400000
            12  6   64000000
            13  6   128000000
            14  7   1280000000
            

            【讨论】:

            • 这个解决方案遗漏了一些组合。例如,它不会检查 i=1,j=2 的情况,任何 i=1 和 j>1 的情况..
            • @Federico:你是对的!难怪我两次谷歌面试都失败了,间隔 6 年,但问题几乎相同:-)
            【解决方案17】:

            这是我的解决方案

            #include <stdio.h>
            #include <math.h>
            #define N_VALUE 5
            #define M_VALUE  5
            
            int n_val_at_m_level[M_VALUE];
            
            int print_lower_level_val(long double val_of_higher_level, int m_level)
            {
            int  n;
            long double my_val;
            
            
            for( n = n_val_at_m_level[m_level]; n <= N_VALUE; n++) {
                my_val =  powl(2,n) * powl(5,m_level);
                if(m_level != M_VALUE && my_val > val_of_higher_level) {
                    n_val_at_m_level[m_level] = n;
                    return 0;
                }
                if( m_level != 0) {
                    print_lower_level_val(my_val, m_level - 1);
                }
                if(my_val < val_of_higher_level || m_level == M_VALUE) {
                    printf("    %Lf n=%d m = %d\n", my_val, n, m_level);
                } else {
                    n_val_at_m_level[m_level] = n;
                    return 0;
                }
             }
             n_val_at_m_level[m_level] = n;
             return 0;
             }
            
            
             main()
             {
                print_lower_level_val(0, M_VALUE); /* to sort 2^n * 5^m */
             }
            

            结果:

            1.000000 n = 0 m = 0
            2.000000 n = 1 m = 0
            4.000000 n = 2 m = 0
            5.000000 n = 0 m = 1
            8.000000 n = 3 m = 0
            10.000000 n = 1 m = 1
            16.000000 n = 4 m = 0
            20.000000 n = 2 m = 1
            25.000000 n = 0 m = 2
            32.000000 n = 5 m = 0
            40.000000 n = 3 m = 1
            50.000000 n = 1 m = 2
            80.000000 n = 4 m = 1
            100.000000 n = 2 m = 2
            125.000000 n = 0 m = 3
            160.000000 n = 5 m = 1
            200.000000 n = 3 m = 2
            250.000000 n = 1 m = 3
            400.000000 n = 4 m = 2
            500.000000 n = 2 m = 3
            625.000000 n = 0 m = 4
            800.000000 n = 5 m = 2
            1000.000000 n = 3 m = 3
            1250.000000 n = 1 m = 4
            2000.000000 n = 4 m = 3
            2500.000000 n = 2 m = 4
            3125.000000 n = 0 m = 5
            4000.000000 n = 5 m = 3
            5000.000000 n = 3 m = 4
            6250.000000 n = 1 m = 5
            10000.000000 n = 4 m = 4
            12500.000000 n = 2 m = 5
            20000.000000 n = 5 m = 4
            25000.000000 n = 3 m = 5
            50000.000000 n = 4 m = 5
            100000.000000 n = 5 m = 5
            

            【讨论】:

              【解决方案18】:

              我知道我可能错了,但这里有一个非常简单的启发式方法,因为它不涉及像 2、3、5 这样的许多数字。我们知道对于任何 i,j 2^i * 5^j 下一个序列将是 2^(i-2) * 5^(j+1)。作为一个 google q,它必须有一个简单的解决方案。

              def func(i, j):
               print i, j, (2**i)*(5**j)
              
              imax=i=2
              j=0
              print "i", "j", "(2**i)*(5**j)"
              
              for k in range(20):
                  func(i,j)
                  j=j+1; i=i-2
                  if(i<0):
                      i = imax = imax+1
                      j=0
              

              这会产生如下输出:

              i j (2**i)*(5**j)
              2 0 4
              0 1 5
              3 0 8
              1 1 10
              4 0 16
              2 1 20
              0 2 25
              5 0 32
              3 1 40
              1 2 50
              6 0 64
              4 1 80
              2 2 100
              0 3 125
              7 0 128
              5 1 160
              3 2 200
              1 3 250
              8 0 256
              6 1 320
              

              【讨论】:

              • 它可能最多工作 20 或 200,但在某些时候它会开始跳过一些数字和/或以错误的顺序输出它们。
              【解决方案19】:

              如果您按照我们在表达式2^i * 5^j 中增加 i 或 j 时实际发生的情况,您要么乘以另一个 2 或另一个 5。如果我们将问题重述为 - 给定 i 和 j 的特定值,如何找到下一个更大的值,解决方案就变得显而易见了。

              以下是我们可以非常直观地列举的规则:

              • 如果表达式中有一对 2 (i &gt; 1),我们应该用 5 替换它们以获得下一个最大的数字。因此,i -= 2j += 1
              • 否则,如果有5(j &gt; 0),我们需要用三个2来代替。所以j -= 1i += 3
              • 否则,我们只需要再提供 2 以将值增加最小值。 i += 1

              这是 Ruby 中的程序:

              i = j = 0                                                                       
              20.times do                                                                     
                puts 2**i * 5**j
              
                if i > 1                                                                      
                  j += 1                                                                      
                  i -= 2                                                                      
                elsif j > 0                                                                   
                  j -= 1                                                                      
                  i += 3                                                                      
                else                                                                          
                  i += 1                                                                      
                end                                                                                                                                                               
              end
              

              【讨论】:

              • 这不起作用,因为 'i' 永远不会大于 4,因此不会出现 32 (2^5) 的倍数。
              【解决方案20】:

              如果我们被允许使用 java Collection 那么我们可以在 O(n^2) 中获得这些数字

              public static void main(String[] args) throws Exception {
                  int powerLimit = 7;  
                   int first = 2;
                   int second = 5;
                  SortedSet<Integer> set = new TreeSet<Integer>();
              
                  for (int i = 0; i < powerLimit; i++) {
                      for (int j = 0; j < powerLimit; j++) {
                          Integer x = (int) (Math.pow(first, i) * Math.pow(second, j));
                          set.add(x);
                      }
                  }
              
                  set=set.headSet((int)Math.pow(first, powerLimit));
              
                  for (int p : set)
                      System.out.println(p);
              }
              

              这里 powerLimit 必须非常小心地初始化!!取决于你想要多少个数字。

              【讨论】:

              • 这会产生错误的结果:在 2^6*5=320 之前缺少 2^8 = 256。枚举区域是三角形的,不是矩形的。
              • @WillNess 怎么样??当我设置 powerLimit=9 时,这个 sn-p 返回以下数字 1 2 4 5 8 10 16 20 25 32 40 50 64 80 100 125 128 160 200 250 256 320 400 500
              • 不,它产生 100 个数字。你怎么知道在哪里停下来?你必须解释这一点。 --- 我在你的代码 sn-p 中提到了 7。要使这成为一个有效的答案,您必须准确解释如何为给定数量的数字设置限制,以及它将过度产生多少数字
              【解决方案21】:

              这是我对 Scala 的尝试:

              case class IndexValue(twosIndex: Int, fivesIndex: Int)
              case class OutputValues(twos: Int, fives: Int, value: Int) {
                def test(): Boolean = {
                  Math.pow(2,  twos) * Math.pow(5, fives) == value
                }
              }
              
              def run(last: IndexValue = IndexValue(0, 0), list: List[OutputValues] = List(OutputValues(0, 0, 1))): List[OutputValues] = {
                if (list.size > 20) {
                  return list
                }
              
                val twosValue = list(last.twosIndex).value * 2
                val fivesValue = list(last.fivesIndex).value * 5
              
                if (twosValue == fivesValue) {
                  val lastIndex = IndexValue(last.twosIndex + 1, last.fivesIndex + 1)
                  val outputValues = OutputValues(value = twosValue, twos = list(last.twosIndex).twos + 1, fives = list(last.fivesIndex).fives + 1)
                  run(lastIndex, list :+ outputValues)
                } else if (twosValue < fivesValue) {
                  val lastIndex = IndexValue(last.twosIndex + 1, last.fivesIndex)
                  val outputValues = OutputValues(value = twosValue, twos = list(last.twosIndex).twos + 1, fives = list(last.twosIndex).fives)
                  run(lastIndex, list :+ outputValues)
                } else {
                  val lastIndex = IndexValue(last.twosIndex, last.fivesIndex + 1)
                  val outputValues = OutputValues(value = fivesValue, twos = list(last.fivesIndex).twos, fives = list(last.fivesIndex).fives + 1)
                  run(lastIndex, list :+ outputValues)
                }
              }
              
              val initialIndex = IndexValue(0, 0)
              run(initialIndex, List(OutputValues(0, 0, 1))) foreach println
              

              输出:

              OutputValues(0,0,1)
              OutputValues(1,0,2)
              OutputValues(2,0,4)
              OutputValues(0,1,5)
              OutputValues(3,0,8)
              OutputValues(1,1,10)
              OutputValues(4,0,16)
              OutputValues(2,1,20)
              OutputValues(0,2,25)
              OutputValues(5,0,32)
              OutputValues(3,1,40)
              OutputValues(1,2,50)
              OutputValues(6,0,64)
              OutputValues(4,1,80)
              OutputValues(2,2,100)
              OutputValues(0,3,125)
              OutputValues(7,0,128)
              OutputValues(5,1,160)
              OutputValues(3,2,200)
              OutputValues(1,3,250)
              OutputValues(8,0,256)
              

              【讨论】:

                猜你喜欢
                • 1970-01-01
                • 1970-01-01
                • 2011-05-06
                • 2011-01-26
                • 2011-11-11
                • 1970-01-01
                • 1970-01-01
                • 2021-11-24
                相关资源
                最近更新 更多