返回

雨天有策:巧用双指针,优化接雨水问题

后端

巧用双指针,优化动态规划:解决接雨水难题

在雨季里,当雨水从屋檐上倾泻而下时,你是否曾留意过在屋檐下形成的那些小水洼?想象一下,如果你被困在屋檐下,而你的目标是最大限度地收集雨水,你会怎么做?

这是一个经典的算法难题,称为“接雨水”问题。给定一组代表屋檐高度的柱子,你需要计算这些柱子可以接住多少雨水。

朴素的动态规划方法

最直接的方法是采用动态规划。对于每个柱子,我们可以计算出它左边和右边最高柱子的高度,然后根据这些高度和柱子本身的高度,计算出它可以接住的雨水量。最后,将所有柱子的接水量相加,即可得到总接水量。

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]。

  1. 初始化两个指针 left = 0 和 right = 11。
  2. 左边最高柱子的高度为 0,右边最高柱子的高度为 1。
  3. 因为左边最高柱子的高度较低,所以我们移动 left,left = 1。
  4. 现在,左边最高柱子的高度为 1。
  5. 现在,左边最高柱子的高度仍然较低,所以我们继续移动 left,left = 2。
  6. 现在,左边最高柱子的高度为 0。
  7. 现在,左边最高柱子的高度仍然较低,所以我们继续移动 left,left = 3。
  8. 现在,左边最高柱子的高度为 2。
  9. 现在,左边最高柱子的高度仍然较低,所以我们继续移动 left,left = 4。
  10. 现在,左边最高柱子的高度为 1。
  11. 现在,左边最高柱子的高度仍然较低,所以我们继续移动 left,left = 5。
  12. 现在,左边最高柱子的高度为 0。
  13. 现在,左边最高柱子的高度仍然较低,所以我们继续移动 left,left = 6。
  14. 现在,左边最高柱子的高度为 1。
  15. 现在,左边最高柱子的高度仍然较低,所以我们继续移动 left,left = 7。
  16. 现在,左边最高柱子的高度为 3。
  17. 现在,左边最高柱子的高度比右边最高柱子的高度高,所以我们移动 right,right = 10。
  18. 现在,左边最高柱子的高度仍然比右边最高柱子的高度高,所以我们继续移动 right,right = 9。
  19. 现在,左边最高柱子的高度仍然比右边最高柱子的高度高,所以我们继续移动 right,right = 8。
  20. 现在,左边最高柱子的高度仍然比右边最高柱子的高度高,所以我们继续移动 right,right = 7。
  21. 现在,左边最高柱子的高度仍然比右边最高柱子的高度高,所以我们继续移动 right,right = 6。
  22. 现在,左边最高柱子的高度仍然比右边最高柱子的高度高,所以我们继续移动 right,right = 5。
  23. 现在,左边最高柱子的高度仍然比右边最高柱子的高度高,所以我们继续移动 right,right = 4。
  24. 现在,左边最高柱子的高度仍然比右边最高柱子的高度高,所以我们继续移动 right,right = 3。
  25. 现在,左边最高柱子的高度仍然比右边最高柱子的高度高,所以我们继续移动 right,right = 2。
  26. 现在,左边最高柱子的高度仍然比右边最高柱子的高度高,所以我们继续移动 right,right = 1。
  27. 现在,左边最高柱子的高度仍然比右边最高柱子的高度高,所以我们继续移动 right,right = 0。
  28. 现在,两个指针相遇,算法结束。

最终,我们计算出的总接水量为 6。

常见问题解答

1. 双指针算法为什么比动态规划算法更有效率?

双指针算法的时间复杂度为 O(n),而动态规划算法的时间复杂度为 O(n^2)。这是因为双指针算法仅需要遍历数组一次,而动态规划算法需要遍历数组两次。

2. 双指针算法是否适用于所有接雨水问题?

是的,双指针算法适用于所有接雨水问题,无论柱子的形状或高度如何。

3. 如果柱子中有负值,双指针算法是否仍然有效?

不,双指针算法不适用于柱子中有负值的情况。这是因为负值会导致接水量的计算出现问题。

4. 如何处理重复高度的柱子?

在处理重复高度的柱子时,双指针算法可能会出现错误。为了解决这个问题,我们可以使用哈希表来记录每个高度出现的次数,并根据出现次数调整接水量的计算。

5. 双指针算法可以用于解决其他问题吗?

是的,双指针算法可以用于解决许多其他问题,例如寻找最大子数组、寻找逆序对的数量等等。