我需要进行一些实时 DFT,而我使用的算法在样本数量可以被分解成小因子时效率最高。
假设我有一个数字 n
和因子 2、3、5
。如何找到最接近(与 n
相比)的数字,其质因数分解中只包含2、3、5
这些数字?
n
几乎总是小于 50,000
,所以暴力枚举可能是一个不错的选择。
在1到50000范围内,只有265个数字可以被2、3、5整除。因此,你可以制作一个小表格,在表格中查找答案。但是,在我的计算机上,平均需要6.5微秒来查找给定数字的最近的2-3-5可整除数,所以我会说暴力破解已经足够好了。
int isValid( int n )
{
while ( n%2 == 0 )
n /= 2;
while ( n%3 == 0 )
n /= 3;
while ( n%5 == 0 )
n /= 5;
return n == 1;
}
int findBest( int n )
{
for ( int i = 0; i < n; i++ )
{
if ( isValid(n+i) )
return n+i;
if ( isValid(n-i) )
return n-i;
}
return 0; // should never get here
}
int main( void )
{
for ( int n = 1; n <= 50000; n++ )
printf( "%d->%d\n", n,findBest(n) );
}
虽然这并没有完全解决问题,即给定一个整数x,它只能找到最接近大于或等于x的没有除了2和3之外的因数(或任何其他给定的因数对)。但是我认为这很巧妙,由于它在O(log x)时间和O(1)空间中运行,因此它可能是有用的! 它的概念类似于Bresenham线算法。 伪代码如下:
为什么这有效呢?请注意,始终有y = 2 ^ i * 3 ^ j,其中i,j >= 0,而且随着时间的推移,i只会减少,而j只会增加。我们在步骤2进入时保持的不变量是“任何值z = 2 ^ a * 3 ^ b,其中a> i或b < j已知为不相关的”(即无效或没有比某个已发现的解决方案更好),因此不需要考虑。 ”这显然在第一次到达步骤2时是正确的,因为y是2的幂并且已经>= x,因此任何具有a > i的2 ^ a * 3 ^ b会是至少2 * y(无论b如何)这比y更差;而且没有整数z可以具有y中少于j = 0个3的幂。
(表达这种不变性的另一种方法是“要么我们已经找到了最优解,要么它就是一些数字z = 2 ^ a * 3 ^ b,其中a <= i且b >= j .“)
如果在第2步中满足条件“y < x”,那么y = 2^i*3^j不是一个有效的解,因为它太低了。更强烈地说,对于任何a <= i,2^a*3^j也不能是有效的解,因为任何这样的解都至少与y一样低。因此,现在我们知道(从不变量中得知)任何满足(a > i OR b < j)的一对(a,b)都是无趣的,并且我们从刚刚成功的“y < x”测试中得知,任何满足(a <= i AND b = j)的一对(a,b)也是无趣的。现在考虑任何满足稍微不同条件(a > i OR b < j+1)的一对(a,b):如果(a,b)不满足无趣性的第一个条件(来自不变量),则我们有((a > i OR b < j+1) AND !(a > i OR b < j)),这通过德摩根定律简化为(b < j+1 AND a <= i AND b >= j)(因为使(a <= i AND b >= j)成立需要(a <= i)为真,强制(a > i)为假,从而允许将其从OR中消除),这显然与(a <= i AND b = j)相同--但这正是我们通过“y < x”测试的成功刚刚建立第二种无趣性的条件。因此,这证明了任何满足(a > i OR b < j+1)的一对(a,b)都是无趣的--这在增加j后恰好变成我们想要保持的不变量。__builtin_clz{,l,ll}
来以与CPU无关的方式完成此操作。 - Ruslan我不确定是否有任何高效的解决方案。以下是寻找最接近n的数字的暴力方法。
int diff=Integer.MAX_VALUE;
int num=0;
public void closestNumber(int n,int curr)
{
if(diff < Math.abs(n -curr) && curr >= n)
return;
if(diff >= Math.abs(n -curr))
{
diff = Math.abs(n -curr);
num=curr;
}
closestNumber(n,curr*2);
closestNumber(n,curr*3);
closestNumber(n,curr*5);
}
closestNumber(n,1);
System.out.println("closest number: "+num);
sum
没有解释。 - Rafaf Tahsin编辑:
以下代码找到离目标最近的可被给定因数集中至少一个数整除的数字。它不能解决明确的目标,即找到仅能被给定因数集整除的最近数字。
原文:
可被2、3或5整除的数字系列是 OEIS A080671,并且具有简单的递归公式a(n+22) = a(n)+30。此外,该系列方便地仅具有单整数间隔。这意味着您只需确定您的数字是否位于其中一个间隔上,并选择下一个或前一个整数。
class NumberFinder
{
public:
NumberFinder()
{
for (int i = 0; i < 2 * 3 * 5; i++)
{
bool hasSmallFactors =
(i % 2 == 0) ||
(i % 3 == 0) ||
(i % 5 == 0);
series.push_back(hasSmallFactors);
}
}
int Find(int n)
{
int x = n % (2 * 3 * 5);
if (series[x]) return n; // already good
return n + 1; // guaranteed to be good
}
private:
vector<bool> series;
};
这也可以推广到任何一组所需的因素:
class NumberFinder
{
public:
NumberFinder(vector<int> factors)
{
product = 1;
for (auto factor : factors) product *= factor;
for (int i = 0; i < product; i++)
{
bool hasSmallFactors = false;
for (auto factor : factors)
{
if (i % factor == 0) hasSmallFactors = true;
}
series.push_back(hasSmallFactors);
}
}
int Find(int n)
{
int lo = n;
int hi = n;
bool found = series[n % product];
while (!found)
{
if (--lo < 0) lo = 0;
hi++;
found = series[hi % product] || series[lo % product];
}
if (series[lo % product]) return lo;
return hi;
}
private:
int product;
vector<bool> series;
};
n
的大小是多少?如果它被限制在32位内,最有效的解决方案可能是预先计算所有候选数字,将它们存储在一个数组中,然后进行二分查找。 - Roland Illig