返回

砖块堆叠优化:O(NlogN) 解法详解

python

优化砖块堆叠问题的时间复杂度

本文将探讨如何优化砖块堆叠问题,目标是将时间复杂度从O(N²)降低到更高效的O(NlogN)。问题核心在于如何将长度各异的砖块堆叠到最少数量的堆中。堆叠的条件是,如果砖块i要放在砖块j上面,则需要满足条件 Ai + x ≤ Aj。 提供的python代码,使用嵌套循环结构造成了较高的计算开销,特别是在砖块数量N较大时。

使用二分查找优化

当前的算法需要为每个砖块遍历所有现有的堆,以便找到合适的堆叠位置。此遍历步骤是造成 O(N²) 时间复杂度的瓶颈。可以利用二分查找加速找到合适的堆的操作。维护一个表示每堆顶部砖块大小的列表,并在插入新砖块时,对这个列表执行二分查找,找出满足 brick + x <= top_brick 条件的最优位置,从而省去对每堆的遍历。如果找到,新砖块放到该堆上;否则创建一个新的堆。这样可以有效地减少时间消耗。

代码示例:

import bisect

def arrange_bricks_optimized(N, x, bricks):
    bricks.sort(reverse=True)
    tops = []  # 记录每堆顶部砖块大小的列表
    stacks = [] # 记录每堆的实际砖块

    for brick in bricks:
        i = bisect.bisect_left(tops, brick + x)  # 使用二分查找寻找合适位置

        if i < len(tops): # 找到可以放置的堆
           tops[i] = brick #更新堆顶的砖块
           stacks[i].append(brick)
        else: # 没有合适的堆,创建一个新的堆
            tops.append(brick)
            stacks.append([brick])


    print(len(stacks))
    for stack in stacks:
        print(len(stack), ' '.join(map(str, stack)))

N, x = map(int, input().split())
bricks = list(map(int, input().split()))
arrange_bricks_optimized(N, x, bricks)

操作步骤:

  1. bisect 模块导入python。
  2. 定义函数 arrange_bricks_optimized 接收 N (砖块总数)、x(偏移量) 以及砖块列表bricks。
  3. 首先,对砖块进行降序排序。
  4. 创建 tops 列表记录每堆顶部砖块的值和stacks列表,用来记录实际的砖块。
  5. 对于每个砖块, 使用 bisect.bisect_lefttops 列表中进行二分查找, 查找比 brick + x 大的最小位置。
  6. 如果找到合适位置(小于tops长度),更新该位置的值为新的brick值,更新对应stack.
  7. 否则创建一个新的堆, 并添加到 tops 与 stacks 中。
  8. 输出堆的数量与每堆内的砖块。

解释:

bisect_left 查找数组中插入元素的位置,保持数组有序,这使得查找过程高效。它把对所有堆进行遍历的 O(N) 复杂度降低到二分查找的 O(logN),从而实现了整体 O(NlogN) 的时间复杂度。

结合优先队列(堆)的优化

使用优先队列(最小堆)也可以提高效率,在代码中可使用 python heapq 库实现。 通过堆结构,能更快速找到满足条件的堆。具体方法如下: 将每一个已有的砖堆最顶上的砖块作为元素添加到堆中,每次尝试放置一个新砖块,找到满足新砖块+x<=堆顶砖块的堆,进行放置和更新。 这样做可以省去遍历所有堆的耗时。 如果找不到合适的,就创建一个新的堆并入堆中。

代码示例:

import heapq

def arrange_bricks_heap(N, x, bricks):
    bricks.sort(reverse=True)
    
    stacks = [] # 用于存放每堆砖块的列表
    heap = []  # 使用堆来存储堆顶的砖块值

    for brick in bricks:
      
      
        found_stack = False
        # 如果当前堆中有元素
        if heap:
           #  检索所有可用的堆顶
            while heap:
                top_brick_val = heapq.heappop(heap)
                top_brick_stack_index= -1 #需要记录这个元素属于那个堆
                
                #堆中的数据记录了这个元素的真实值和 所在stack中的索引
                if isinstance(top_brick_val,tuple):
                    top_brick_val,top_brick_stack_index = top_brick_val

                
                if brick+ x<=top_brick_val : #当前元素可以放在此堆上
                    
                   found_stack=True
                   stacks[top_brick_stack_index].append(brick)  #更新当前堆的砖块
                   heapq.heappush(heap,(brick,top_brick_stack_index)) # 把当前新的砖块加入到堆顶
                   break; 
            # 如果遍历完所有堆顶的砖都找不到合适的堆,就需要新建一个堆
            if not found_stack:
                 stacks.append([brick]) #新增加一个堆
                 heapq.heappush(heap, (brick,len(stacks) - 1) )# 把新的砖块加到堆顶
        else:  # 初始化 第一个堆 
           stacks.append([brick])
           heapq.heappush(heap,(brick,0))
        

    print(len(stacks))
    for stack in stacks:
       print(len(stack), ' '.join(map(str, stack)))

N, x = map(int, input().split())
bricks = list(map(int, input().split()))
arrange_bricks_heap(N, x, bricks)

操作步骤:

  1. 引入 heapq 库实现堆操作。
  2. 对砖块按长度降序排序。
  3. 创建一个堆heap 和列表 stacksheap 记录的是砖堆堆顶元素与所在堆的index
  4. 遍历每个砖块,如果heap 中存在值,循环找出能满足 brick + x <= top_brick 的砖堆。如果当前堆无法放入当前元素,则heapq.heappop(heap)移除并检测下个砖堆的堆顶。
  5. 如果能找到,更新对应stack的值,并把当前砖块,与stack索引推入堆顶;如果没有找到适合的堆,则创建一个新的砖堆。将新砖块及其所在stack index添加到堆。
  6. 最后输出砖堆的数量与各砖堆内的砖块。

解释:
在堆优化过程中,优先队列维护所有可用堆的最上层砖,并总是优先尝试向最小砖块堆上堆叠。由于 heapq 提供的是 O(logN) 的堆操作时间,此方法把平均复杂度降低到O(NlogN)。

结论

上述方法都将原始O(N²) 的时间复杂度降低到 O(NlogN),这显著地提高了算法的效率,特别是在处理大量数据时。选用哪个方案取决于实际情况和个人偏好。在数据量较小的时候,使用二分查找更为简洁。而堆结构可以应对更加复杂的情况。对于本题而言,二分查找算法实现起来更为简单,效率也符合预期要求。

需要注意的点是: 输入的数据范围。在真实场景中要考虑程序对于错误数据的兼容性。例如要加入合理的边界检查、类型校验等,增强程序的稳定性。此外对于实际的数据要提前做统计,以便预判计算资源。