给定X,确定n,其中X是第n个丑数

时间:2019-04-29 01:50:36

标签: c++ algorithm math optimization numbers

  

“丑陋数字是唯一素数为2、3或5的数字。   顺序1、2、3、4、5、6、8、9、10、12、15…显示了第一个丑陋的11   数字。按照惯例,其中包括1。“

给出数字X,确定该序列中X的顺序。 示例:X = 12,输出:10。

我制作了一种蛮力算法,可以在O(XlogX)中运行:

long long cnt = 0;
for(long long i = 1; i<X; i++)
{
  long long tmp = i;
  while(tmp % 2 == 0) tmp/=2;
  while(tmp % 3 == 0) tmp/=3;
  while(tmp % 5 == 0) tmp/=5;
  if(tmp == 1) cnt ++;
}
cout << cnt+1 << endl;

但是,X可能是1e18,可能有10 ^ 5个查询,每个查询给我们一个数字X。

有人知道执行此操作的更有效算法吗?谢谢。

1 个答案:

答案 0 :(得分:0)

单次查询

您可以使用以下算法,通过计算n下面的丑陋数字的数量来获得X的位置X

int get_position(long long X)
{
    int n = 0;
    for(long long n2=1; n2<=X; n2*=2)
        for(long long n3=n2; n3<=X; n3*=3)
            for(long long n5=n3; n5<=X; n5*=5)
                ++n;
    return n;
}

该算法循环遍历2、3和5的所有倍数,并以O(n)(其中n~log(X)³)运行。

多个查询

如果要重复多次操作,可以保留一个表以执行二进制搜索:

struct ugly_numbers
{
    std::vector<long long> numbers{1};

    int get_position(long long X)
    {
        if(X>numbers.back())
        {
            std::set<long long> number_set;
            for(long long n2=1; n2<=X; n2*=2)
                for(long long n3=n2; n3<=X; n3*=3)
                    for(long long n5=n3; n5<=X; n5*=5)
                        number_set.insert(n5);
            numbers.assign(number_set.begin(), number_set.end());
        }
        auto value_it = std::upper_bound(numbers.begin(),numbers.end(),X);
        return (int)std::distance(numbers.begin(),value_it);
    }
};

当数字是高速缓存的一部分时,此算法在O(log(n))中运行,而当需要重新创建高速缓存时,该算法在O(n*log(n))中运行。另外,您可以使用最大期望数量预先创建缓存,以分摊缓存创建的成本。

避免溢出

对于数量接近类型long long的最大值的数字查询,可能会发生溢出错误和无限循环。为避免这种情况,请使用以下代码(并对摊销版本使用类似的逻辑):

int get_position(long long X)
{
    int n=0;
    long long max_n2 = std::min(X,std::numeric_limits<long long>::max() / 2);
    long long max_n3 = std::min(X,std::numeric_limits<long long>::max() / 3);
    long long max_n5 = std::min(X,std::numeric_limits<long long>::max() / 5);
    for(long long n2=1; n2<=max_n2; n2*=2)
        for(long long n3=n2; n3<=max_n3; n3*=3)
            for(long long n5=n3; n5<=max_n5; n5*=5)
                ++n;
    return n;
}