制作代码O(256)?

时间:2015-08-04 19:22:19

标签: python algorithm time

  

问题陈述(HackerRank

     

你有一个整数列表,最初列表是空的。

     

您必须处理三种类型的Q操作:

     
      
  • add s:将整数s添加到列表中,注意整数可以更多   列表中的一次

  •   
  • del s:从列表中删除一个整数s的副本,这是有保证的   列表中至少会有一个s副本。

  •   
  • cnt s:计算列表中有多少个整数,以便得到AND   s = a,其中AND是按位AND运算符

  •   
     

输入格式

     

第一行包含整数Q.以下Q行中的每一行   包含操作类型字符串T和整数s。

     

约束

     
      
  • 1≤Q≤200000
  •   
  • 0≤s< 2 16
  •   
     

输出格式

     

对于每个 cnt s 操作,请在新行中输出答案。

     

示例输入

7 
add 11 
cnt 15 
add 4 
add 0 
cnt 6 
del 4 
cnt 15
     

示例输出

1 
2 
2
     

解释

     

对于第一行,我们有15 AND 11 = 11所以答案是1

     

对于第二行,6 AND 0 = 0和6 AND 4 = 4,所以答案是2

     

对于第三行,已删除4,我们有15 AND 11 = 11和15   AND 0 = 0所以答案是2

我的工作代码:

operations = int(raw_input())
current = 1
lst = []
while current <= operations:
    count = 0
    input_ = raw_input().split()
    operation = input_[0]
    num = int(input_[1])
    if operation == 'add':
        lst.append(num)
    elif operation == 'cnt':
        for number in lst:
            if number & num == number:
                count += 1
        print(count)
    elif operation == 'del':
        lst.remove(num)
    current += 1

有些优化的解决方案:CodeReview Answer

operations = int(raw_input())
nums = set()
storage = [0] * (2 ** 16)

for _ in xrange(operations): 
    input_ = raw_input().split()
    operation = input_[0]
    num = int(input_[1])

    if operation == 'add':
        nums |= {num}
        storage[num] += 1
    elif operation == 'cnt':
        print(sum(
            storage[number]
            for number in nums
            if (number & num) == number
        ))
    elif operation == 'del':
        storage[num] -= 1

我理解上面的代码和所做的优化。

我不理解的代码:

q = input()
def init():
    global a
    a = [0 for i in range(1000000)]

def add(s):
    for i in range(1 << 8):
        if (i&s) == 0:
            a[s|i] += 1

def delete(s):
    for i in range(1 << 8):
        if (i&s) == 0:
            a[s|i] -= 1
def cnt(s):
    res = 0
    for i in range(1 << 8):
        if (s|(i<<8)) == s:
            res += a[s & (~(i<<8))]
    return res

init()
for _ in range(q):
    c,s = map(str,raw_input().split())
    s = int(s)
    if c == "add":
        add(s)
    elif c == "del":
        delete(s)
    elif c == "cnt":
        print cnt(s)

我无法理解此代码的作者如何使用给定的操作使其成为O(256)。

根据排行榜,上面的代码是Python中两个可接受的提交之一。第二个解决方案(非常类似于上面的解决方案):HackerRank Solution

1 个答案:

答案 0 :(得分:4)

该计划实际上是O(q),其中q个操作中的每一个都在O(1)处理,其常数系数为256 = 2^8 = 8 bitsO(256)是一种离合/非正式的说法。

由于s最多为2^16,我们可以通过仅存储每个数字的第一个16位来解决问题。这将是这样的:

def add(s):
    a[s & ((1 << 16) - 1)] += 1 # only save the first 16 bits

def delete(s):
    a[s & ((1 << 16) - 1)] -= 1 # only remove the first 16 bits

def cnt(s):
    res = 0
    for i in range(1 << 16): # also O(1), but with a large constant
        if i & s == i:
            res += a[i]
    return res

然后,您将在O(q)中找到一个解决方案,但其常数因子2^16大于解决方案中的2^8。那么我们如何优化上述内容呢?首先,让我们看看每个函数究竟发生了什么。

def add(s):
    for i in range(1 << 8): # 1 << 8 is just a fancy way of saying 2 ** 8, or 256
        if (i&s) == 0:
            a[s|i] += 1

这意味着,对于每个8位数i,如果AND编辑s为{0}(这意味着两个没有设置1 {1}})共同位),然后我们将s OR d保存为该数字(这意味着s获得i的设置位并保持自己的位数)。保存在这里意味着增加该索引的计数。

例如,如果二进制为i = 10

i = 0000 0000 => 10 & i == 0 => a[0000 0010] = 1
i = 0000 0001 => 10 & i == 0 => a[0000 0011] = 1
i = 0000 0010 => 10 & i != 0
i = 0000 0011 => 10 & i != 0
i = 0000 0100 => 10 & i == 0 => a[0000 0110] = 1
...
a[...  **** **1*] = 1 

执行此操作后,我们增加了每个数字的计数,如果使用AND进行10,则会导致10。所以基本上,对于每次添加s,我们预先计算cnt(s)的答案。但是在某种程度上,不仅s是预先计算的,而且二进制表示中的值也是相似的值。

此外,我们还计算了未必添加到列表中的数字。但我们稍后会解决这个问题。

def delete(s):
    for i in range(1 << 8):
        if (i&s) == 0:
            a[s|i] -= 1

这与上述相反。

def cnt(s):
    res = 0
    for i in range(1 << 8):
        if (s|(i<<8)) == s:
            res += a[s & (~(i<<8))]
    return res

在这里,我们需要查看列表p中有多少个AND s p仍为8,我们必须通过检查if (s | (i << 8)) == s 位数。

i

由于8i << 8位,i = **** **** i << 8 = **** **** 0000 0000 将如下所示:

s

使用此OR 8会将更多位添加到其最后s = 10位(或者它们将保持不变)。例如,如果10 | (0 << 8) == 10 => true 10 | (1 << 8) == 10 => false ... the rest should actually all be false ,我们将:

a[s & (~(i << 8))]

现在,我们将增加计数:

i = 0

i = 0000 0000 i << 8 = 0000 0000 0000 0000 ~(i << 8) = 1111 1111 1111 1111 10 & (~(i << 8)) = 1111 1111 1111 1111 & 10 = 0000 0000 0000 0010 a[0000 0000 0000 0010] = 1

add

基本上,这会反转add中的编码并找到答案。请记住,在8中,我们没有触及最后s & (~(i << 8))位。在这里,我们通过执行3删除最后一位,只留下列表中的数字。

它背后的直觉是你可以在n操作中传播工作。我不知道如何给你一个精确的扣除。通常,当您必须使用n / 2位数时,只需使用n / 2位数和32运算,就可以对它们进行大量的计数操作。我通常只是捣乱这些公式直到某些东西粘住,或者我放弃并寻找答案。不是很正式,但是。

一旦你有了解决方案,理解它只需要掌握位操作的确切内容。这可能需要一些时间,但如果你有足够的时间来完成它,它将是一个更加精确的过程。

为了更深入地了解其工作原理,我建议您为方法的每个部分添加打印语句,这些语句会准确显示所发生的事情以及一切如何,就像我尝试的那样。然后使用小输入运行程序。

您可以以类似的方式解决的另一个问题是:给定一堆1位数,快速回答每个位有多少个(O(1))位。使用2^16的常数因子在do { try AVAudioSession.sharedInstance().setActive(true) } catch let err as NSError { println("Dim background error") } 中执行此操作。