砖块堆叠优化:O(NlogN) 解法详解
2025-01-24 16:21:58
优化砖块堆叠问题的时间复杂度
本文将探讨如何优化砖块堆叠问题,目标是将时间复杂度从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)
操作步骤:
- 将
bisect
模块导入python。 - 定义函数
arrange_bricks_optimized
接收 N (砖块总数)、x(偏移量) 以及砖块列表bricks。 - 首先,对砖块进行降序排序。
- 创建
tops
列表记录每堆顶部砖块的值和stacks列表,用来记录实际的砖块。 - 对于每个砖块, 使用
bisect.bisect_left
在tops
列表中进行二分查找, 查找比brick + x
大的最小位置。 - 如果找到合适位置(小于tops长度),更新该位置的值为新的brick值,更新对应stack.
- 否则创建一个新的堆, 并添加到 tops 与 stacks 中。
- 输出堆的数量与每堆内的砖块。
解释:
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)
操作步骤:
- 引入
heapq
库实现堆操作。 - 对砖块按长度降序排序。
- 创建一个堆
heap
和列表stacks
,heap
记录的是砖堆堆顶元素与所在堆的index - 遍历每个砖块,如果
heap
中存在值,循环找出能满足brick + x <= top_brick
的砖堆。如果当前堆无法放入当前元素,则heapq.heappop(heap)
移除并检测下个砖堆的堆顶。 - 如果能找到,更新对应stack的值,并把当前砖块,与stack索引推入堆顶;如果没有找到适合的堆,则创建一个新的砖堆。将新砖块及其所在stack index添加到堆。
- 最后输出砖堆的数量与各砖堆内的砖块。
解释:
在堆优化过程中,优先队列维护所有可用堆的最上层砖,并总是优先尝试向最小砖块堆上堆叠。由于 heapq
提供的是 O(logN) 的堆操作时间,此方法把平均复杂度降低到O(NlogN)。
结论
上述方法都将原始O(N²) 的时间复杂度降低到 O(NlogN),这显著地提高了算法的效率,特别是在处理大量数据时。选用哪个方案取决于实际情况和个人偏好。在数据量较小的时候,使用二分查找更为简洁。而堆结构可以应对更加复杂的情况。对于本题而言,二分查找算法实现起来更为简单,效率也符合预期要求。
需要注意的点是: 输入的数据范围。在真实场景中要考虑程序对于错误数据的兼容性。例如要加入合理的边界检查、类型校验等,增强程序的稳定性。此外对于实际的数据要提前做统计,以便预判计算资源。