序列中的第N个1

时间:2018-01-13 22:45:18

标签: python python-3.x algorithm

长度为t的数组将所有元素初始化为1。现在我们可以对数组执行两种类型的查询

  1. i索引处的元素替换为0.此查询由0 index
  2. 表示
  3. 在新行上查找并打印一个整数,表示数组A中第k个1的索引;如果没有这样的索引,则打印-1。此查询用1 k
  4. 表示

    现在假设对于长度为t=4的数组,其开头的所有元素现在都是[1,1,1,1],现在查询0 2数组变为[1,0,1,1],查询1 3输出结果为4

    我使用过暴力方法,但如何提高代码效率呢?

    n,q=4,2
    arr=[1]*4
    for i in range(q):
        a,b=map(int,input().split())
        if a==0:
            arr[b-1]=0
        else:
            flag=True
            count=0
            target=b
            for i,j in enumerate(arr):
                if j ==1:
                    count+=1
                    if count==target:
                        print(i+1)
                        flag=False
                        break
            if flag:
                print(-1)
    

    我还尝试先在列表中附加1的所有索引,然后进行二分查找,但弹出0会更改代码失败的索引

    def binary_search(low,high,b):
        while(low<=high):
            mid=((high+low)//2)
            #print(mid)
            if mid+1==b:
               print(stack[mid]+1)
               return
            elif mid+1>b:
                high=mid-1
            else:
                low=mid+1
    
    
    n=int(input())
    q=int(input())
    stack=list(range(n))
    for i in range(q):
        a,b=map(int,input().split())
        if a==0:
            stack.pop(b-1)
            print(stack)
    
        else:
            if len(stack)<b:
                print(-1)
                continue
            else:
                low=0
                high=len(stack)-1
                binary_search(low,high,b)
    

4 个答案:

答案 0 :(得分:3)

您可以构建一个二叉树,其中每个节点都会为您提供位于其下方和左侧的数量。因此,如果 n 为7,那么该树最初看起来像这样(所有的实际列表都显示在它下面):

         4
      /    \
    2        2
   / \      / \
  1   1    1   1
 ----------------
 1 1 1 1  1 1 1 -

将索引4(从零开始)的数组元素设置为0,会将该树更改为:

         4
      /    \
    2        1*
   / \      / \
  1   1    0*  1
 ----------------
 1 1 1 1  0*1 1 -

因此,设置0表示 O(log(n))时间复杂度。

通过对节点值求和,同时沿着正确的方向向下降树,可以在相同的时间复杂度下计算1的数量。

这是您可以使用的Python代码。它代表list in breadth-first order中的树。我没有竭尽全力进一步优化代码,但它具有上述时间复杂性:

class Ones:
    def __init__(self, n): # O(n)
        self.lst = [1] * n
        self.one_count = n
        self.tree = []
        self.size = 1 << (n-1).bit_length()
        at_left = self.size // 2
        width = 1
        while width <= at_left:
            self.tree.extend([at_left//width] * width)
            width *= 2

    def clear_index(self, i): # O(logn)
        if i >= len(self.lst) or self.lst[i] == 0:
            return
        self.one_count -= 1
        self.lst[i] = 0        
        # Update tree 
        j = 0
        bit = self.size >> 1
        while bit >= 1:
            go_right = (i & bit) > 0
            if not go_right:
                self.tree[j] -= 1
            j = j*2 + 1 + go_right
            bit >>= 1

    def get_index_of_ith_one(self, num_ones): # O(logn)
        if num_ones <= 0 or num_ones > self.one_count:
            return -1
        j = 0
        k = 0
        bit = self.size >> 1
        while bit >= 1:
            go_right = num_ones > self.tree[j]
            if go_right:
                k |= bit
                num_ones -= self.tree[j]
            j = j*2 + 1 + go_right
            bit >>= 1
        return k

    def is_consistent(self): # Only for debugging
        # Check that list can be derived by calling get_index_of_ith_one for all i 
        lst = [0] * len(self.lst)
        for i in range(1, self.one_count+1):
            lst[self.get_index_of_ith_one(i)] = 1
        return lst == self.lst

# Example use
ones = Ones(12)
print('tree', ones.tree)
ones.clear_index(5)
ones.clear_index(2)
ones.clear_index(1)
ones.clear_index(10)
print('tree', ones.tree)
print('lst', ones.lst)
print('consistent = ', ones.is_consistent())

请注意,这会将索引视为从零开始,而方法get_index_of_ith_one需要一个至少为1的参数(但它返回一个从零开始的索引)。

应该很容易适应您的需求。

复杂性

  • 创作:O(n)
  • 清除索引:O(登录)
  • 获取索引:O(logn)
  • 空间复杂度:O(n)

答案 1 :(得分:2)

让我们从一些常规技巧开始:

  • 在迭代之前检查第n个元素是否对于列表来说太大。如果您还保留一个存储零个数的“计数器”,您甚至可以检查nth >= len(the_list) - number_of_zeros(不确定>=是否正确,这个示例似乎使用了基于1的索引,所以我可能是一个一个)。这样,只要使用太大的值,就可以节省时间。

  • 使用更高效的功能。

    因此,您可以使用input代替sys.stdin.readline而不是binary_search(请注意它将包含尾随换行符)。

    而且,即使它在这种情况下可能没有用,内置的bisect模块也会比你创建的for _ in itertools.repeat(None, q)函数更好。

    您也可以使用for i in range(q)代替if j,这样会更快,而且您不需要该索引。

然后,您可以使用一些更专业的事实来改进代码:

  • 您只存储0和1,因此您可以使用if not j检查1和n来检查零。这些比手动比较快一点,尤其是当你在循环中这样做时。

  • 每次查找第n个1时,您都可以创建一个包含遇到的O(1) s +索引的临时字典(或列表)。然后重复使用该dict进行后续查询(dict-lookup和list-random-access为O(n),而搜索为for index, item in enumerate(arr):)。如果您有后续查询而没有中间更改,您甚至可以扩展它。

    但是,如果发生更改,您需要丢弃该字典(或列表)或更新它。

一些挑剔:

  • 变量名称不是很具描述性,您可以使用i代替jarr

  • 您使用的是列表,因此i是一个误导性的变量名称。

  • 您有两个enumerate个变数。

但不要误会我的意思。这是一次非常好的尝试,而且您使用range代替{{1}}这一事实非常棒,并且表明您已经编写了pythonic代码。

答案 2 :(得分:2)

考虑类似于区间树的东西:

  • 根节点覆盖整个数组
  • 子节点分别覆盖父范围的左半部分和右半部分
  • 每个节点保存其范围内的数量

替换和搜索查询都可以在对数时间内完成。

答案 3 :(得分:0)

使用较少的行进行重构,因此在行数方面更有效,但运行时间可能与O(n)相同。

n,q=4,2
arr=[1]*4
for i in range(q):
    query, target = map(int,input('query target: ').split())
    if query == 0:
        arr[target-1] = 0
    else:
        count=0
        items = enumerate(arr, 1)
        try:
            while count < target:
                index, item = next(items)
                count += item
        except StopIteration as e:
            index = -1
        print(index)

假设arr仅包含1和0 - 在将其添加到计数之前,您不必检查项目是否为1,添加零无效。

要检查标记,只需在枚举对象(items)上继续调用,直到达到目标或arr结尾。

为了提高运行效率,使用外部库但基本相同的过程(算法):

import numpy as np
for i in range(q):
    query, target = map(int,input('query target: ').split())
    if query == 0:
        arr[target-1] = 0
    else:
        index = -1
        a = np.array(arr).cumsum() == target
        if np.any(a):
            index = np.argmax(a) + 1
        print(index)