受this question on mathoverflow启发
假设我有一个 n x n 乘法表,它上面的不同值的数量是多少?
例如,3X3乘法表
1 2 3
2 4 6
3 6 9
有 6 唯一值,即 [1,2,3,4,6,9]
到目前为止,我只有 O(n 2 )解决方案
public static void findDistinctNumbers(int n) {
Set<Integer> unique = new HashSet<>();
for(int i=1; i<=n; i++) {
for(int j=1; j<=n; j++) {
unique.add(i*j);
}
}
System.out.println("number of unique values: " + unique.size());
}
是否有更好的方法,小于 O(n 2 )?
答案 0 :(得分:6)
问题在于,要使用集合来验证唯一性,您必须先填充它,然后以某种方式填充O(n ^ 2); 没有一套,你就不能轻易验证一个数字是否是唯一的......
作为旁注:因为大O类有点宽泛(即它可以描述任何复杂性不高于而不是某些东西,但不一定 不低,即。线性复杂度和二次复杂度算法都可以描述为O(n ^ 2),因为在两种情况下都是复杂性 不高于n ^ 2) - 因此,假设此答案中的每个O(x)表示&#34; Big Theta&#34;,即。渐近上/下边界,这样 f(n)在O(g(n))中意味着k1 * g(n)<= f(n)<= k2 * g(n)(当然k1,k2为正)。
正如https://mathoverflow.net/questions/108912/number-of-elements-in-the-set-1-cdots-n-times-1-cdots-n?lq=1指出的那样,确切数量是
渐近逼近一个众所周知的价值;即便如此,任何给定n
的精确值都不是可以简单计算的值,
实质上,它与解决http://en.wikipedia.org/wiki/Prime-counting_function -
说,让我们试着总结事实,&#34;(为什么?)&#34;标记字段我懒得/累了解释ATM,但感兴趣的读者 可以验证(或反驳)自己:
a)没有众所周知的公式来通过一个简单的函数a(n)来获得结果,
b)因此,我们需要生成一个包含所有唯一数字的集合,并返回该集合的基数作为结果,
c)因为集合中的实际数量被证明是o(n ^ 2)(参见参考文献),严格来说o(n ^ 2 /(log n)^ c *(loglog n)^ 3/2), 生成集合将至少采取那么多操作 - 这是我们的下限 - 假设我们已经知道数字是否在集合中,
d)因此,我们的算法A的复杂度C充其量只能是
O(n ^ 2)> O(C)> O(n ^ 2 /(log n)^ c *(loglog n)^ 3/2)(请注意,这仅代表微小相对于纯n ^ 2的改进)。
那就是说,我对A的提议如下:
a)由于矩阵与对角线对称,因此我们假设我们只分析例如右上角三角加上对角线
b)假设对于你的n,任何数字x =&lt; n在集合
中c)计算y = int(sqrt(n)) - 行r <= y的每个对角线值已经存在于集合中,行r的每个对角线值> 1。 ÿ 必须检查
c&#39;)n *(n + 1)/ 2-n-int(sqrt(n))元素需要在&#34;常规&#34;中进行处理(添加到集合)。方法
d)现在,由于我们排除了所有可以轻松预测的值,我们进入主循环: for(row r&lt; n)// max number是r * n 所有x:x&gt; (r-1)* n保证是唯一的到现在为止,所以它们不需要处理,假设我们不必设置唯一的数字!< / em>的; 由于行的集合用于数字(r ^ 2; r * n),因此行r中的所有数字((r-1) n,r n)都在范围内 现在,由于行r中的实际数字组是a_n = r,2 * r,3 * r ... n r,显然问题是找到 a&#34;边界&#34;整数y r使得y * r> (r-1)* n,因为这意味着我们有n-y保证唯一身份。 如果我们找到((r-1)* n)/ r的精确值为整数,我们可以安全地假设y =((r-1)* n)/ r + 1(为什么?), 并且该确切整数不是唯一的。 因此,每一行都有确保最大(n-r,ceil(n / r))唯一的唯一(为什么?);我们在每行的O(1)中得到这个
e)最棘手的部分:我们有一些&gt; =比r * r,但明显小于(r-1) n; 这是&#34;硬范围&#34;,[r r,(r-1)* n),其中数字可以是唯一的,也可以不是唯一的; 我们最多可以有i_r = max(0,n-r-floor(n / r))个数来检查这个范围(为什么?) 甚至天真地检查这个范围内的每个数字显然比O(n)快(为什么?-floor(n / r)因子相对于n增长!)我们已经比O(n ^ 2)好了 - 我们有sum(i_r)次迭代,对于r = 2..n(第一行是无操作),所以这实际上等于 r = 2..n的总和(max(0,n-r-floor(n / r))) - 我不会在这里提供精确的复杂性类结果,因为它不是一个漂亮的数字, 让我们进一步努力......
f)弹射器怎么样?
g)对于奇数行,我们不能做更多的事情(因为在很多事情中,这需要我们解决一些与素数有关的问题 已经在评论中提到的问题,这些问题还没有为世界上最好的马赛克人解决过) - 但我们仍然可以 为每一个人提供帮助!
将r除以2。每个&lt; = r / 2 * n 的数字都已经处理完毕!它是独一无二的,我们不必关心!。
请注意,由于我们实际上已经删除了行的末尾(也是大多数开头),因此效果非常好。 由于我们只检查偶数行,我们只是开始检查它们(添加到set)而不是从x = r *(r + 1),而是从r / 2 * n + r开始检查!
h)但是现在,最重要的是:如果我们没有找到一组已经发现的独特定义,如何检查它们?遗憾的是,这是主要的 任何算法试图低于~n * n / 2个元素迭代的问题 - 由于您不处理所有值,您如何知道该值是否已被处理?
i)如果有一种简单的方法可以预测有多少(例如%)可能独特的&#34;数字真的很独特,这里不会有任何真正的问题, 这将是一个O(n)问题 - 但由于上述困难,我认为这是不可能的。
tl; dr - 我打电话给恶作剧试试严格低于O(n ^ 2)的任何答案 - 你可以放下几位,但复杂性等级无论如何都不会减少。
答案 1 :(得分:0)
我确信有一个比O(n²)更好的解决方案。我一直无法找到它,但我相信我走的是正确的道路,只有一些数学见解。
这是我的算法开发中:
public static int numberOfDistinctResults(int n) {
if (n == 1) {
return 1;
}
// runs a prime-number sieve in O(n log log n)
Factorization.initialize(n);
int total = 1;
for (int i = 2; i <= n; i++) {
total += i;
// divs[i] == 0 iff i is prime, or the lowest prime that divides i
if (Factorization.divs[i] == 0) {
// i is prime; for all j<=i, j*i is brand new; nothing to substract
} else {
// i is non-prime; discard already-seen decompositions
total -= magic(n); // <--- this part needs work
}
}
return total;
}
目前,只要magic(n)
可以在O(n)下运行,它就会在O(n²)下运行。但是,我一直无法找到这样的magic
函数。基本上,我是从oeis.org/A062854添加术语 - 该图中似乎有很多结构。
有关整个问题的大量背景信息,请参阅oeis.org/A027424(列出的所有算法似乎都是O(n²)),magic(n)
表示{{1}的值列表和几个相关的序列。接下来是一个小的值表,您可以看到素数很容易(0到减去),但非素数很难(与分解或现有因素没有明显的关系):
f(1) = 1 ( prev + 1 = 1 - 0)
f(2) = 3 ( prev + 2 = 2 - 0)
f(3) = 6 ( prev + 3 = 3 - 0)
f(4) = 9 ( prev + 3 = 4 - 1)
4 = 2^2; available = 2·3
f(5) = 14 ( prev + 5 = 5 - 0)
f(6) = 18 ( prev + 4 = 6 - 2)
6 = 2·3; available = 2^2·3·5
f(7) = 25 ( prev + 7 = 7 - 0)
f(8) = 30 ( prev + 5 = 8 - 3)
8 = 2^3; available = 2^2·3·5·7
f(9) = 36 ( prev + 6 = 9 - 3)
9 = 3^2; available = 2^3·3·5·7
f(10) = 42 ( prev + 6 = 10 - 4)
10 = 2·5; available = 2^3·3^2·5·7
f(11) = 53 ( prev + 11 = 11 - 0)
f(12) = 59 ( prev + 6 = 12 - 6)
12 = 2^2·3; available = 2^3·3^2·5·7·11
f(13) = 72 ( prev + 13 = 13 - 0)
f(14) = 80 ( prev + 8 = 14 - 6)
14 = 2·7; available = 2^3·3^2·5·7·11·13
f(15) = 89 ( prev + 9 = 15 - 6)
15 = 3·5; available = 2^3·3^2·5·7·11·13
f(16) = 97 ( prev + 8 = 16 - 8)
16 = 2^4; available = 2^3·3^2·5·7·11·13
f(17) = 114 ( prev + 17 = 17 - 0)
f(18) = 123 ( prev + 9 = 18 - 9)
18 = 2·3^2; available = 2^4·3^2·5·7·11·13·17
f(19) = 142 ( prev + 19 = 19 - 0)
f(20) = 152 ( prev + 10 = 20 - 10)
20 = 2^2·5; available = 2^4·3^2·5·7·11·13·17·19
f(21) = 164 ( prev + 12 = 21 - 9)
21 = 3·7; available = 2^4·3^2·5·7·11·13·17·19
f(22) = 176 ( prev + 12 = 22 - 10)
22 = 2·11; available = 2^4·3^2·5·7·11·13·17·19
f(23) = 199 ( prev + 23 = 23 - 0)
f(24) = 209 ( prev + 10 = 24 - 14)
24 = 2^3·3; available = 2^4·3^2·5·7·11·13·17·19·23
f(25) = 225 ( prev + 16 = 25 - 9)
25 = 5^2; available = 2^4·3^2·5·7·11·13·17·19·23
f(26) = 239 ( prev + 14 = 26 - 12)
26 = 2·13; available = 2^4·3^2·5^2·7·11·13·17·19·23
f(27) = 254 ( prev + 15 = 27 - 12)
27 = 3^3; available = 2^4·3^2·5^2·7·11·13·17·19·23
f(28) = 267 ( prev + 13 = 28 - 15)
28 = 2^2·7; available = 2^4·3^3·5^2·7·11·13·17·19·23
f(29) = 296 ( prev + 29 = 29 - 0)
f(30) = 308 ( prev + 12 = 30 - 18)
30 = 2·3·5; available = 2^4·3^3·5^2·7·11·13·17·19·23·29
f(31) = 339 ( prev + 31 = 31 - 0)
f(32) = 354 ( prev + 15 = 32 - 17)
32 = 2^5; available = 2^4·3^3·5^2·7·11·13·17·19·23·29·31
f(33) = 372 ( prev + 18 = 33 - 15)
33 = 3·11; available = 2^5·3^3·5^2·7·11·13·17·19·23·29·31
f(34) = 390 ( prev + 18 = 34 - 16)
34 = 2·17; available = 2^5·3^3·5^2·7·11·13·17·19·23·29·31
f(35) = 410 ( prev + 20 = 35 - 15)
35 = 5·7; available = 2^5·3^3·5^2·7·11·13·17·19·23·29·31
f(36) = 423 ( prev + 13 = 36 - 23)
36 = 2^2·3^2; available = 2^5·3^3·5^2·7·11·13·17·19·23·29·31
f(37) = 460 ( prev + 37 = 37 - 0)
f(38) = 480 ( prev + 20 = 38 - 18)
38 = 2·19; available = 2^5·3^3·5^2·7·11·13·17·19·23·29·31·37
f(39) = 501 ( prev + 21 = 39 - 18)
39 = 3·13; available = 2^5·3^3·5^2·7·11·13·17·19·23·29·31·37
答案 2 :(得分:-1)
是的,有一个更好的算法,它运行在次二次时间。请参阅 Brent, Pomerance, Purdum, and Webster 最近的这篇论文。他们的算法还输出 n x n 乘法表中的所有值。请注意,由于 Erdős 的经典结果,已知值的数量是次二次的。 MathOverflow 上的链接 question 有更多信息。