以递增的顺序打印形式2 ^ i * 5 ^ j的数字

时间:2011-09-27 15:45:31

标签: algorithm smooth-numbers

如何按递增顺序打印2^i * 5^j形式的数字。

For eg:
1, 2, 4, 5, 8, 10, 16, 20

10 个答案:

答案 0 :(得分:2)

这实际上是一个非常有趣的问题,特别是如果你不希望这是N ^ 2或NlogN的复杂性。

我将做的是以下内容:

  • 定义包含2个值(i和j)以及公式结果的数据结构。
  • 定义包含此数据结构的集合(例如std :: vector)
  • 使用值(0,0)初始化集合(在这种情况下结果为1)
  • 现在循环执行以下操作:
    • 查看集合并获取具有最小值的实例
    • 将其从集合中删除
    • 打印出来
    • 根据您刚处理的实例创建2个新实例
      • 在第一个实例中增加i
      • 在第二个实例中增加j
    • 将两个实例添加到集合中(如果它们不在集合中)
  • 循环,直到你有足够的时间

通过选择正确的数据结构和集合,可以轻松调整性能。 例如。在C ++中,您可以使用std :: map,其中键是公式的结果,值是对(i,j)。取最小值然后只取第一个实例(* map.begin())。

我快速编写了以下应用程序来说明它(它有效!但不包含其他评论,抱歉):

#include <math.h>
#include <map>
#include <iostream>

typedef __int64 Integer;

typedef std::pair<Integer,Integer> MyPair;
typedef std::map<Integer,MyPair> MyMap;

Integer result(const MyPair &myPair)
{
return pow((double)2,(double)myPair.first) * pow((double)5,(double)myPair.second);
}

int main()
{
MyMap myMap;
MyPair firstValue(0,0);

myMap[result(firstValue)] = firstValue;

while (true)
   {
   auto it=myMap.begin();
   if (it->first < 0) break;        // overflow

   MyPair myPair = it->second;
   std::cout << it->first << "= 2^" << myPair.first << "*5^" << myPair.second << std::endl;

   myMap.erase(it);

   MyPair pair1 = myPair;
   ++pair1.first;
   myMap[result(pair1)] = pair1;

   MyPair pair2 = myPair;
   ++pair2.second;
   myMap[result(pair2)] = pair2;
   }
}

答案 1 :(得分:2)

这非常适合函数式编程风格。在F#中:

let min (a,b)= if(a<b)then a else b;;
type stream (current, next)=
    member this.current = current
    member this.next():stream = next();;
let rec merge(a:stream,b:stream)=
    if(a.current<b.current) then new stream(a.current, fun()->merge(a.next(),b))
    else new stream(b.current, fun()->merge(a,b.next()));;

let rec Squares(start) = new stream(start,fun()->Squares(start*2));;

let rec AllPowers(start) = new stream(start,fun()->merge(Squares(start*2),AllPowers(start*5)));;
let Results = AllPowers(1);;

适用于结果,然后是具有当前值和下一个方法的流类型。

走过它:

  1. 我为minnes定义了min。
  2. 我定义了一个流类型,它具有一个当前值和一个返回一个新字符串的方法,基本上是一个数字流的头尾。
  3. 我定义了函数merge,它接受两个流的当前值中较小的一个,然后递增该流。然后递归以提供流的其余部分。基本上,给定两个按顺序排列的流,它将生成一个按顺序排列的新流。
  4. 我将正方形定义为以2的幂增加的流。
  5. AllPowers获取起始值,并将所有正方形得到的流合并为此数量为5.它与将其乘以5得到的流,因为这是您唯一的两个选项。你实际上留下了一个结果树
  6. 结果是合并越来越多的流,因此您合并以下流

    1,2,4,8,16,32 ......

    5,10,20,40,80,160 ......

    25,50,100,200,400 ......

    。 通过尾递归和编译器优化等来合并所有这些变得相当有效。

    这些可以像这样打印到控制台:

    let rec PrintAll(s:stream)=
        if (s.current > 0) then
            do System.Console.WriteLine(s.current)
            PrintAll(s.next());;
    
    PrintAll(Results);
    
    let v = System.Console.ReadLine();
    

    类似的事情可以用任何语言来完成,它允许递归并将函数作为值传递(如果你不能将函数作为变量传递,它只会稍微复杂一些)。

答案 2 :(得分:2)

对于O(N)解决方案,您可以使用到目前为止找到的数字列表和两个索引:一个表示要乘以2的下一个数字,另一个表示要乘以5的下一个数字。然后在每次迭代你有两个候选值来从中选择较小的一个。

在Python中:

 numbers = [1]
 next_2 = 0
 next_5 = 0

 for i in xrange(100):
     mult_2 = numbers[next_2]*2
     mult_5 = numbers[next_5]*5

     if mult_2 < mult_5:
        next = mult_2
        next_2 += 1
     else:
        next = mult_5
        next_5 += 1

     # The comparison here is to avoid appending duplicates
     if next > numbers[-1]:
        numbers.append(next)

 print numbers

答案 3 :(得分:1)

所以我们有两个循环,一个递增i,第二个递增j从零开始,对吧? (乘以符号在问题的标题中令人困惑)

你可以做一些非常简单的事情:

  1. 添加数组中的所有项目
  2. 对数组进行排序
  3. 或者你需要一个有更多数学分析的其他解决方案?

    编辑:利用与Merge Sort问题的相似性提供更智能的解决方案

    如果我们将2^i5^j的无限数量想象为两个独立的流/列表,则此问题看起来与众所周知的Merge Sort问题非常相似。

    解决方案步骤如下:

    • 从每个流(2个和5个)中获取两个数字
    • 比较
    • 返回最小的
    • 从先前返回的最小
    • 的流中获取下一个数字

    就是这样! ;)

    PS:合并排序always的复杂程度为O(n*log(n))

答案 4 :(得分:1)

我将此问题可视化为矩阵M M(i,j) = 2^i * 5^j。这意味着行和列都在增加。

考虑按递增顺序在条目中画一条线,显然从条目(1,1)开始。当您访问条目时,行和列增加条件可确保由这些单元格形成的形状始终为integer partition(英文表示法)。跟踪此分区(mu = (m1, m2, m3, ...),其中mi是行i中较小条目的数量 - 因此m1 >= m2 >= ...)。然后,您需要比较的唯一条目是可以添加到分区的条目。

这是一个粗略的例子。假设您访问了所有x s(mu = (5,3,3,1)),那么您只需检查@ s:

x x x x x @
x x x @
x x x 
x @
@

因此,支票的数量是可添加单元格的数量(相当于Bruhat order的上升方式的数量,如果你想要用posets来思考的话。)

给定分区mu,可以很容易地确定可添加状态是什么。在最后一个正面条目之后成像0 s的无限字符串。然后,当且仅当mi时,您可以1增加m(i-1) > mi

回到示例,对于mu = (5,3,3,1),我们可以增加m1 (6,3,3,1)m2 (5,4,3,1)m4 (5,3,3,2)m5 (5,3,3,1,1)

然后问题的解决方案找到正确的分区序列(饱和链)。在伪代码中:

mu = [1,0,0,...,0];
while (/* some terminate condition or go on forever */) {
    minNext = 0;
    nextCell = [];
    // look through all addable cells
    for (int i=0; i<mu.length; ++i) {
        if (i==0 or mu[i-1]>mu[i]) {
            // check for new minimum value
            if (minNext == 0 or 2^i * 5^(mu[i]+1) < minNext) {
                nextCell = i;
                minNext = 2^i * 5^(mu[i]+1)
            }
        }
    }
    // print next largest entry and update mu
    print(minNext);
    mu[i]++;
}

我在Maple中写了这个,经过12次迭代后停止了:

1, 2, 4, 5, 8, 10, 16, 20, 25, 32, 40, 50

并输出了输出的细胞序列并得到了这个:

1  2  3  5  7 10
4  6  8  11 
9  12

对应于此矩阵表示:

1, 2, 4, 8, 16, 32...

5, 10, 20, 40, 80, 160...

25, 50, 100, 200, 400...

答案 5 :(得分:0)

首先,(正如其他人已经提到的)这个问题很模糊!

尽管如此,我将根据您的模糊等式和模式作为预期结果进行拍摄。因此,我不确定以下内容是否符合您的要求,但它可能会让您对Java集合有所了解!

import java.util.List;
import java.util.ArrayList;
import java.util.SortedSet;
import java.util.TreeSet;


public class IncreasingNumbers {

    private static List<Integer> findIncreasingNumbers(int maxIteration) {
        SortedSet<Integer> numbers = new TreeSet<Integer>();
        SortedSet<Integer> numbers2 = new TreeSet<Integer>();

        for (int i=0;i < maxIteration;i++) {
            int n1 = (int)Math.pow(2, i);
            numbers.add(n1);

            for (int j=0;j < maxIteration;j++) {
                int n2 = (int)Math.pow(5, i);
                numbers.add(n2);

                for (Integer n: numbers) {
                    int n3 = n*n1;
                    numbers2.add(n3);
                }
            }
        }

        numbers.addAll(numbers2);

        return new ArrayList<Integer>(numbers);
    }

    /**
     * Based on the following fuzzy question @ StackOverflow
     * http://stackoverflow.com/questions/7571934/printing-numbers-of-the-form-2i-5j-in-increasing-order
     * 
     * 
     * Result:
     * 1 2 4 5 8 10 16 20 25 32 40 64 80 100 125 128 200 256 400 625 1000 2000 10000 
     */
    public static void main(String[] args) {
        List<Integer> numbers = findIncreasingNumbers(5);

        for (Integer i: numbers) {
            System.out.print(i + " ");
        }
    }
}

答案 6 :(得分:0)

如果你能在O(nlogn)中做到这一点,这里有一个简单的解决方案:

Get an empty min-heap
Put 1 in the heap
while (you want to continue)
    Get num from heap
    print num
    put num*2 and num*5 in the heap

你有它。通过min-heap,我的意思是min-heap

答案 7 :(得分:0)

作为一名数学家,在看这样的事情时,我总是会想到的第一件事是“对数会有帮助吗?”。

在这种情况下可能会。

如果我们的系列A增加,那么系列日志(A)也在增加。由于A的所有项都是2 ^ i.5 ^ j的形式,因此系列log(A)的所有成员的形式为i.log(2)+ j.log(5)

然后我们可以查看系列日志(A)/ log(2),它也在增加,其元素的形式为i + j。(log(5)/ log(2))

如果我们计算出为最后一个系列生成完整有序列表的i和j(称之为B),那么i和j也将正确生成A系列。

这只是改变问题的本质,但希望能够更容易解决问题。在每一步,您可以增加i并减少j,反之亦然。

看看你可以做出的一些早期变化(我可能会将其称为i,j的变换或只是变换)给我们提供了一些我们前进的线索。

显然将i增加1将使B增加1.但是,如果log(5)/ log(2)约为2.3,则将j增加1而将i减小2将增加仅0.3。然后问题是在每个阶段找到B对i和j的变化的最小可能增加。

为了做到这一点,我只保留了一个记录,因为我增加了i和j的最有效变换(即从每个变换中添加和减去的变换)以获得系列中最小的可能增加。然后应用任何一个有效(即确保i和j不变为负数)。

因为在每个阶段你可以减少i或减少j,实际上有两类变换可以单独检查。新的转换不一定要包含在我们未来的检查中,只比其他同类检查更好。

为了测试我的思想,我在LinqPad中编写了一个程序。需要注意的一点是,Dump()方法只是将对象输出到屏幕,并且语法/结构对于真正的c#文件无效。如果你想运行它,转换它应该很容易。

希望任何未明确解释的内容都可以从代码中理解。

void Main()
{
    double C = Math.Log(5)/Math.Log(2);
    int i = 0;
    int j = 0;
    int maxi = i;
    int maxj = j;

    List<int> outputList = new List<int>();
    List<Transform> transforms = new List<Transform>();
    outputList.Add(1);
    while (outputList.Count<500)
    {
    Transform tr;
        if (i==maxi)
        {
            //We haven't considered i this big before. Lets see if we can find an efficient transform by getting this many i and taking away some j.
            maxi++;
            tr = new Transform(maxi, (int)(-(maxi-maxi%C)/C), maxi%C);
            AddIfWorthwhile(transforms, tr);
        }
        if (j==maxj)
        {
            //We haven't considered j this big before. Lets see if we can find an efficient transform by getting this many j and taking away some i.
            maxj++;
            tr = new Transform((int)(-(maxj*C)), maxj, (maxj*C)%1);
            AddIfWorthwhile(transforms, tr);
        }
        //We have a set of transforms. We first find ones that are valid then order them by score and take the first (smallest) one.
        Transform bestTransform = transforms.Where(x=>x.I>=-i && x.J >=-j).OrderBy(x=>x.Score).First();
        //Apply transform
        i+=bestTransform.I;
        j+=bestTransform.J;
        //output the next number in out list.
        int value = GetValue(i,j);
        //This line just gets it to stop when it overflows. I would have expected an exception but maybe LinqPad does magic with them?
        if (value<0) break;
        outputList.Add(value);
    }
    outputList.Dump();

}

public int GetValue(int i, int j)
{
    return (int)(Math.Pow(2,i)*Math.Pow(5,j));
}

public void AddIfWorthwhile(List<Transform> list, Transform tr)
{
    if (list.Where(x=>(x.Score<tr.Score && x.IncreaseI == tr.IncreaseI)).Count()==0)
    {
        list.Add(tr);
    }
}

// Define other methods and classes here
    public class Transform
    {
        public int I;
        public int J;
        public double Score;
        public bool IncreaseI
        {
            get {return I>0;}
        }

        public Transform(int i, int j, double score)
        {
            I=i;
            J=j;
            Score=score;
        }
    }

我没有费心去看这个效率,但我强烈怀疑它比其他一些解决方案更好,因为在每个阶段,我需要做的就是检查我的变换集 - 计算出这些变换中有多少与“n”是非平凡的。它显然是相关的,因为你越往前就会有更多的变换,但是新变换的数量在更高的数字时变得越来越小,所以也许它只是O(1)。这个O东西总是让我困惑。 ; - )

其他解决方案的一个优点是,它允许您计算i,j而无需计算产品,从而可以计算出序列的内容,而无需计算实际数字本身。

在前230个nunmbers之后它的价值(当int空间用完时)我每次都要进行9次变换检查。并且考虑到它只有我的总数溢出,我跑了,如果为第一百万个结果,并得到i = 5191和j = 354。变换的数量是23.该列表中该数字的大小约为10 ^ 1810。达到这个水平的运行时间约为5秒。

P.S。如果你喜欢这个答案,请随时告诉你的朋友,因为我花了很多年的时间和几个+ 1s将是很好的补偿。或者实际上只是评论告诉我你的想法。 :)

答案 8 :(得分:0)

我确信现在每个人都可能得到答案,但只是想给这个解决方案指明一点......

这是一个Ctrl C + Ctrl V来自 http://www.careercup.com/question?id=16378662

 void print(int N)
  {
     int arr[N];
     arr[0] = 1;
     int i = 0, j = 0, k = 1;
     int numJ, numI;
     int num;
       for(int count = 1; count < N; )
        {
          numI = arr[i] * 2;
          numJ = arr[j] * 5;

            if(numI < numJ)
             {
               num = numI;
               i++;
             }

           else
            {
              num = numJ;
              j++;
            }

            if(num > arr[k-1])
            {
             arr[k] = num;
             k++;
             count++;
            }

       }

     for(int counter = 0; counter < N; counter++)
     {
      printf("%d ", arr[counter]);
     }
}

答案 9 :(得分:0)

我提出的问题是回归无限的解决方案。我考虑使用树木,但觉得在确定何时收获和修剪树木方面存在问题,因为我有无数的价值观。学家我意识到可以使用筛分算法。从零开始,确定每个正整数是否具有i和j的值。通过转动answer =(2 ^ i)*(2 ^ j)并解决i来促进这一点。这给了我i = log2(答案/(5 ^ j))。这是代码:

class Program
{
static void Main(string[] args)
{
    var startTime = DateTime.Now;

    int potential = 0;

    do
    {
        if (ExistsIandJ(potential))
            Console.WriteLine("{0}", potential);
            potential++;
    } while (potential < 100000);

    Console.WriteLine("Took {0} seconds", DateTime.Now.Subtract(startTime).TotalSeconds);

}

private static bool ExistsIandJ(int potential)
{
    // potential = (2^i)*(5^j)
    // 1 = (2^i)*(5^j)/potential
    // 1/(2^1) = (5^j)/potential or (2^i) = potential / (5^j)
    // i = log2 (potential / (5^j))

    for (var j = 0; Math.Pow(5,j) <= potential; j++)
    {
        var i = Math.Log(potential / Math.Pow(5, j), 2);
        if (i == Math.Truncate(i))
            return true;
    }
    return false;
}
}