在这里了解什么是变量和什么是常数是至关重要的。
位数是一个常数 (10)。所有数字集的数量(1024)也是如此。此类集合的所有对的数量也是如此(2 20或大约一百万)。让我们利用这一点。
让我们尝试将输入预处理为大小恒定(与输入大小无关)的数据结构中的“等效”表示。然后,我们对这个恒定大小的结构所做的任何事情都被定义为恒定时间操作,因此总运行时间仅由预处理阶段渐近确定。
数据结构
创建一个1024个整数的数组,每个桶(索引)对应一组数字;我们希望在每个桶中存储恰好具有该组数字的输入数字的数量。
例如,3606 有数字 0、3 和 6,因此它将被计入存储桶 2 0 + 2 3 + 2 6 = 73。
算法
预处理是显而易见的。取下一个数字(例如'3'
),将其转换为它的值(例如3
),现在计算相应的位(例如1 << 3
)并将其 OR 为(暂定)桶索引变量;不同的数字占据不同的位,因此每个数字组合都有一个唯一的桶,但我们摆脱了任何重复的数字。像这样循环,直到遇到数字分隔符;那时,桶索引是最终的,我们可以增加桶,重置桶索引并跳到下一个数字。
而已。剩下的就是数我们的羊了。哎呀。成对的羊。
将每个存储桶与其他存储桶(但不是自身)进行比较。每当两个索引共享一个数字时(这可以使用&
运算符确定),将这两个存储桶的内容相乘,并将乘积添加到全局维护的总和中。
将每个桶也与其自身进行比较,并仅x * (x - 1) / 2
将其与全局维护的总和相加,其中x
是桶的内容。
这个总和就是你的结果。
表现
最坏的情况:输入大小 O(n)
在哪里。n
常数因素也是有利的。每个数字或分隔符我们需要一些指令(和 RAM 访问);恒定阶段检查一百万个桶对,在每对桶上花费一些类似的指令(不需要访问 RAM,数据结构非常紧凑)。这是闪电般的速度。
理论家会说这是作弊。我们假设输入长度没有上限(或者我们根本不能谈论渐近复杂度),但我们还假设我们可以将总输入长度放入一个整数变量中。那好吧。
更实际的程序员会注意到该算法的字母大小呈指数级。我们很幸运;如果我们的单词不是由数字组成,而是由除分隔符之外的任意字符组成,那么我们的单词仍将是渐近线性时间算法,但与可以轻松处理到兆字节的幼稚算法相比,它对于任何输入都会非常慢一次输入。