【问题标题】:Given X, determine n, where X is n-th ugly number给定 X,确定 n,其中 X 是第 n 个丑数
【发布时间】:2019-09-17 15:34:32
【问题描述】:

“丑数是只有质因数为 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。

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

【问题讨论】:

  • 此类问题通常需要您了解、研究或发现一个偷偷摸摸的数学技巧,从而大大减少您需要执行的计算量。
  • 谢谢。我已经在 O(11000) 中生成了前 11000 个丑陋的数字并且它起作用了。第 11000 个丑数 > 1e18。

标签: c++ algorithm math optimization numbers


【解决方案1】:

单一查询

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

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;
}

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2014-02-09
    • 2017-04-13
    • 1970-01-01
    • 2020-03-03
    • 1970-01-01
    • 2022-01-17
    相关资源
    最近更新 更多