雨天有策:巧用双指针,优化接雨水问题
2023-10-09 01:03:37
巧用双指针,优化动态规划:解决接雨水难题
在雨季里,当雨水从屋檐上倾泻而下时,你是否曾留意过在屋檐下形成的那些小水洼?想象一下,如果你被困在屋檐下,而你的目标是最大限度地收集雨水,你会怎么做?
这是一个经典的算法难题,称为“接雨水”问题。给定一组代表屋檐高度的柱子,你需要计算这些柱子可以接住多少雨水。
朴素的动态规划方法
最直接的方法是采用动态规划。对于每个柱子,我们可以计算出它左边和右边最高柱子的高度,然后根据这些高度和柱子本身的高度,计算出它可以接住的雨水量。最后,将所有柱子的接水量相加,即可得到总接水量。
def trap(height):
dp = [0] * len(height)
left_max = [0] * len(height)
left_max[0] = height[0]
for i in range(1, len(height)):
left_max[i] = max(left_max[i - 1], height[i])
right_max = [0] * len(height)
right_max[len(height) - 1] = height[len(height) - 1]
for i in range(len(height) - 2, -1, -1):
right_max[i] = max(right_max[i + 1], height[i])
for i in range(1, len(height) - 1):
dp[i] = min(left_max[i], right_max[i]) - height[i]
return sum(dp)
这种方法虽然简单,但时间复杂度却高达 O(n^2),其中 n 是柱子的数量。
双指针优化
为了提高效率,我们可以利用双指针优化技术。我们从数组的两端开始,分别指向左边和右边最高柱子。然后,我们移动较矮的指针,计算两指针之间的接水量。当较矮的指针移动到数组末尾时,我们移动较高的指针,直到两个指针相遇。
def trap(height):
left, right = 0, len(height) - 1
left_max, right_max = height[left], height[right]
total = 0
while left < right:
if left_max < right_max:
left += 1
left_max = max(left_max, height[left])
total += left_max - height[left]
else:
right -= 1
right_max = max(right_max, height[right])
total += right_max - height[right]
return total
这种双指针优化算法的时间复杂度为 O(n),效率大幅提升。
实例演示
让我们用一个例子来演示双指针优化算法。假设我们有一组柱子,高度为 [0,1,0,2,1,0,1,3,2,1,2,1]。
- 初始化两个指针 left = 0 和 right = 11。
- 左边最高柱子的高度为 0,右边最高柱子的高度为 1。
- 因为左边最高柱子的高度较低,所以我们移动 left,left = 1。
- 现在,左边最高柱子的高度为 1。
- 现在,左边最高柱子的高度仍然较低,所以我们继续移动 left,left = 2。
- 现在,左边最高柱子的高度为 0。
- 现在,左边最高柱子的高度仍然较低,所以我们继续移动 left,left = 3。
- 现在,左边最高柱子的高度为 2。
- 现在,左边最高柱子的高度仍然较低,所以我们继续移动 left,left = 4。
- 现在,左边最高柱子的高度为 1。
- 现在,左边最高柱子的高度仍然较低,所以我们继续移动 left,left = 5。
- 现在,左边最高柱子的高度为 0。
- 现在,左边最高柱子的高度仍然较低,所以我们继续移动 left,left = 6。
- 现在,左边最高柱子的高度为 1。
- 现在,左边最高柱子的高度仍然较低,所以我们继续移动 left,left = 7。
- 现在,左边最高柱子的高度为 3。
- 现在,左边最高柱子的高度比右边最高柱子的高度高,所以我们移动 right,right = 10。
- 现在,左边最高柱子的高度仍然比右边最高柱子的高度高,所以我们继续移动 right,right = 9。
- 现在,左边最高柱子的高度仍然比右边最高柱子的高度高,所以我们继续移动 right,right = 8。
- 现在,左边最高柱子的高度仍然比右边最高柱子的高度高,所以我们继续移动 right,right = 7。
- 现在,左边最高柱子的高度仍然比右边最高柱子的高度高,所以我们继续移动 right,right = 6。
- 现在,左边最高柱子的高度仍然比右边最高柱子的高度高,所以我们继续移动 right,right = 5。
- 现在,左边最高柱子的高度仍然比右边最高柱子的高度高,所以我们继续移动 right,right = 4。
- 现在,左边最高柱子的高度仍然比右边最高柱子的高度高,所以我们继续移动 right,right = 3。
- 现在,左边最高柱子的高度仍然比右边最高柱子的高度高,所以我们继续移动 right,right = 2。
- 现在,左边最高柱子的高度仍然比右边最高柱子的高度高,所以我们继续移动 right,right = 1。
- 现在,左边最高柱子的高度仍然比右边最高柱子的高度高,所以我们继续移动 right,right = 0。
- 现在,两个指针相遇,算法结束。
最终,我们计算出的总接水量为 6。
常见问题解答
1. 双指针算法为什么比动态规划算法更有效率?
双指针算法的时间复杂度为 O(n),而动态规划算法的时间复杂度为 O(n^2)。这是因为双指针算法仅需要遍历数组一次,而动态规划算法需要遍历数组两次。
2. 双指针算法是否适用于所有接雨水问题?
是的,双指针算法适用于所有接雨水问题,无论柱子的形状或高度如何。
3. 如果柱子中有负值,双指针算法是否仍然有效?
不,双指针算法不适用于柱子中有负值的情况。这是因为负值会导致接水量的计算出现问题。
4. 如何处理重复高度的柱子?
在处理重复高度的柱子时,双指针算法可能会出现错误。为了解决这个问题,我们可以使用哈希表来记录每个高度出现的次数,并根据出现次数调整接水量的计算。
5. 双指针算法可以用于解决其他问题吗?
是的,双指针算法可以用于解决许多其他问题,例如寻找最大子数组、寻找逆序对的数量等等。