从暴力到动态规划再到双指针:使用 Java 探索接雨水问题的不同解法
文章目录
- 一、问题描述
- 二、暴力法(Brute Force)
- 思路
- 实现代码
- 三、动态规划法(Dynamic Programming)
- 思路
- 实现代码
- 四、双指针法(Two Pointers)
- 思路
- 实现代码
- 五、方法对比
在本文中,我们将探讨经典的接雨水问题,并提供多种解题思路和方法。通过逐步深入的方式,将了解到如何使用暴力法、动态规划法以及双指针法解决这一问题,并对它们进行性能对比。
一、问题描述
42.接雨水
二、暴力法(Brute Force)
思路
对于数组中的每一个元素,我们都可以找到它左边和右边的最大高度,然后根据这两个最大高度来确定当前元素能够接住的水量。
实现代码
public int trap(int[] height) {
int totalWater = 0;
for (int i = 1; i < height.length - 1; i++) {
int maxLeft = 0, maxRight = 0;
// 寻找左边最高的柱子
for (int j = i; j >= 0; j--) {
maxLeft = Math.max(maxLeft, height[j]);
}
// 寻找右边最高的柱子
for (int j = i; j < height.length; j++) {
maxRight = Math.max(maxRight, height[j]);
}
// 计算当前位置可以存储的水
totalWater += Math.min(maxLeft, maxRight) - height[i];
}
return totalWater;
}
时间复杂度:O(n^2),因为对于每个元素都需要遍历其左右两边来寻找最大值。
空间复杂度:O(1)。
三、动态规划法(Dynamic Programming)
思路
为了避免重复计算每个位置的左右最大高度,我们可以预先计算并存储这些信息。这样,当我们计算某个位置能接多少水时,可以直接查表得到左右最大高度。
实现代码
public int trap(int[] height) {
if (height == null || height.length == 0) return 0;
int n = height.length;
int[] leftMax = new int[n], rightMax = new int[n];
// 初始化最左边和最右边的边界条件
leftMax[0] = height[0];
rightMax[n - 1] = height[n - 1];
// 填充leftMax数组
for (int i = 1; i < n; i++) {
leftMax[i] = Math.max(leftMax[i - 1], height[i]);
}
// 填充rightMax数组
for (int i = n - 2; i >= 0; i--) {
rightMax[i] = Math.max(rightMax[i + 1], height[i]);
}
// 计算总储水量
int totalWater = 0;
for (int i = 0; i < n; i++) {
totalWater += Math.min(leftMax[i], rightMax[i]) - height[i];
}
return totalWater;
}
时间复杂度:O(n),因为我们只需要遍历数组三次。
空间复杂度:O(n),需要额外的空间来存储左右最大高度。
四、双指针法(Two Pointers)
思路
利用两个指针分别从数组的两端向中间移动,并同时维护左右两侧的最大高度。这种方法可以在一次遍历中解决问题,无需额外的空间。
实现代码
public int trap(int[] height) {
if (height == null || height.length == 0) return 0;
int left = 0, right = height.length - 1;
int maxLeft = 0, maxRight = 0;
int totalWater = 0;
while (left < right) {
if (height[left] < height[right]) {
if (height[left] >= maxLeft) {
maxLeft = height[left];
} else {
totalWater += maxLeft - height[left];
}
left++;
} else {
if (height[right] >= maxRight) {
maxRight = height[right];
} else {
totalWater += maxRight - height[right];
}
right--;
}
}
return totalWater;
}
时间复杂度:O(n),只需遍历一次数组。
空间复杂度:O(1),只使用了常数级别的额外空间。
五、方法对比
方法 | 时间复杂度 | 空间复杂度 | 优点 | 缺点 |
---|---|---|---|---|
暴力法 | O(n^2) | O(1) | 易于理解和实现 | 效率低下,不适用于大数据集 |
动态规划法 | O(n) | O(n) | 高效处理中等规模数据 | 需要额外的存储空间 |
双指针法 | O(n) | O(1) | 最优的时间和空间复杂度 | 理解难度较高 |
通过上述三种方法的介绍和比较,我们可以看到双指针法以其优秀的时空复杂度成为解决该问题的最佳选择。然而,理解不同的方法有助于我们在面对类似问题时能够灵活运用算法思维,选择最适合当前场景的解决方案。