当青训营遇上码上掘金,我选择攒青豆吃。 看到题目的第一时间我想到了11. 盛最多水的容器。
暴力求解
遇到题目先写一个暴力求解,不仅可以熟悉一下题目,还能作为答案生成器来使用。
public static int powerKill(int[] height, int left, int right) {
if (left >= right -1) {
return 0;
}
int maxIndex = left;
for (int i = left + 1; i <= right; i++) {
if (height[i] > height[maxIndex]) {
maxIndex = i;
}
}
int secondIndex = left;
if (maxIndex == left) {
secondIndex =left + 1;
}
for (int i = left; i <= right; i++) {
if (height[i] >= height[secondIndex] && i != maxIndex) {
secondIndex = i;
}
}
int first = 0;
int second = 0;
if (maxIndex > secondIndex) {
first = secondIndex;
second = maxIndex;
} else {
first = maxIndex;
second = secondIndex ;
}
int t = Math.min(height[first], height[second]);
int count = 0;
for (int i = first + 1; i < second; i++) {
count += t -height[i];
}
return count + powerKill(height, left, first) + powerKill(height, second, right);
}
两次遍历
我想本题首先可以转化成若干个类似于11. 盛最多水的容器的子问题:找到最高的两个index,时间复杂度为O(n),计算两者之间能承载的豆子,递归计算两index外侧的结果并且求和。所以如果每次找到的两个index恰好可以在待求范围的中央,则这个问题可以转化成一个时间复杂度为O(nlogn)的问题,最坏的情况也就是O(n2)。(当然,有大佬发现更类似42. 接雨水,原谅我是菜鸡)。 那么能不能在O(n)的时间复杂度解决这个问题呢?
当我尝试一次遍历就得到结果的时候,我发现在遍历的过程中,如果后面一定有一个最高的位置,高度为H,那么从左侧到最高所能攒到的豆子是可以计算的:
维护一个当前最高高度h,遇到下一个最高高度之前,每个位置i所能攒的豆子是h - height[i] 而在最高高度H之后,可以通过反向遍历,即从height.length - 1到 indexOfH 来进行计算。
代码实现:
public static int getBean(int[] height){
if (height == null || height.length ==0) {
return 0;
}
int ans = 0, maxIndex = 0;
int prev = 0;
// find highest index
for (int i = 0; i < height.length; i++) {
if (height[i] > height[maxIndex]) {
maxIndex = i;
}
}
// count highest left from left to right (highest)
for (int i = 0; i <= maxIndex; i++) {
if (height[i] >= prev) {
prev = height[i];
}else {
ans += prev - height[i];
}
}
// count from right to left (highest)
prev = 0;
for (int i = height.length - 1; i >= maxIndex; i--) {
if (height[i] >= prev) {
prev = height[i];
}else {
ans += prev - height[i];
}
}
return ans;
}
以上是我的第一个版本。
一次遍历解决问题!
在后面看了大佬的解答后,我发现其实可以只用一次遍历就得到答案的办法。即从两侧的两个指针left, right向indexOfH靠近,每次移动对应高度较小的指针,直到两个指针相遇。
public static int finalVersion(int[] height) {
if (height == null || height.length ==0) {
return 0;
}
int left = 0, right = height.length - 1;
int leftMax = height[left], rightMax = height[right], res = 0;
while (left < right) {
if (leftMax < rightMax) {
if (height[left] <= leftMax) {
res += leftMax - height[left++];
} else {
leftMax = height[left];
}
} else {
if (height[right] <= rightMax) {
res += rightMax - height[right--];
} else {
rightMax = height[right];
}
}
}
return res;
}
对于边界条件需要注意的是:
left == right的时候循环就要停下了- 在进入循环后的第二个if条件中,需要
height[left] <= leftMax在条件中加入=,因为如果不加的话则会永远停在这一步,并且在高点H有多个的时候,无法继续计算。
写在最后
如果不能自己想出好的解决办法,那么能够在网上找到更好的解决办法,也是很棒的!这样鼓励自己吧~
含对数器版: