这是我参与「第五届青训营 」伴学笔记创作活动的第 1 天
春节假期过的飞快,快乐的日子一去不复返,只在我的肚子上留下了又一圈肥肉,作为自己来过的证明(哭)。年前一直在摸鱼,没怎么看训练营的视频,今天终于闲下来打算写写笔记(主要是再不写笔记就要拿不到结营证书了),点开活动页面一看,嚯,攒(jie)青(yu)豆(shui)。
这道题也是一道非常经典的题目了啊,大家懂得都懂,这里就不复制粘贴题目了,说(chao)一下我之前的题解。
众所周知啊,人往高处走,水往低处流,这天上掉下来的青豆也是如此,如果没有高度合适的柱子把它挡住,就会从两边掉下去。但是吧,这个青豆又是非常遵循木桶原则的,哪边低往哪边滚,另外一边多高都一样。举个例子,height=[1,0,2],那这就只能在中间这一格接一个单位的青(yu)豆(shui),就算你把最右边的高度加到10, 20, 1024, 65535也没用,最后都只能留下一格青豆。也就是说,任意一格能存储的青豆的数量,完全取决于两侧最高点中,较低一侧的高度。我们可以自然而然地想到第一种解法:用数组遍历记录某个位置两侧的最高点,然后遍历计算最终结果。Java代码如下所示:
public static int collectBeans(int[] height) {
int n = height.length, ans = 0;
int[] left = new int[n], right = new int[n];
left[0] = height[0];
right[n - 1] = height[n - 1];
for (int i = 1; i < n; ++i) {
left[i] = Math.max(left[i - 1], height[i]);
}
for (int i = n - 2; i >= 0; --i) {
right[i] = Math.max(right[i + 1], height[i]);
}
for (int i = 0; i < n; ++i) {
ans += Math.min(left[i], right[i]) - height[i];
}
return ans;
}
写完之后我们可以注意到,我们完全可以在计算right数组的同时,计算存储青豆的数量。这样一来,right数组实际上是可以被优化成单个变量的(但left数组不能直接优化),如下所示:
int n = height.length, ans = 0, right;
int[] left = new int[n];
left[0] = height[0];
right = height[n - 1];
for (int i = 1; i < n; ++i) {
left[i] = Math.max(left[i - 1], height[i]);
}
for (int i = n - 2; i >= 0; --i) {
right = Math.max(right, height[i]);
ans += Math.min(left[i], right) - height[i];
}
return ans;
}
细心的同学已经注意到了,我说left数组不能直接优化,没说不能优化啊。接下来讲解一个,可能不太好懂的解法,双指针。
双指针的核心思想是我们在开头说到的,任意一格能存储的青豆的数量,完全取决于两侧最高点中,较低一侧的高度。在上面的方法中,我们在计算left数组的时候,不知道该点右侧的最高高度是多少,也就不能直接得到该点能存储的青豆的量。但是,如果我们能保证右侧一定比左侧高呢?双指针用的就是这样一种思想。初始时,用l和r两个变量,分别指向0和n-1的位置,并用lh和rh分别记录两侧最大高度。两个指针分别向中间移动。移动时,先判断两侧高度,如果左侧最大高度小于右侧最大高度,则计算左指针当前位置的储水量(右边的最高一定高于左边的最高,因此当前左指针指向位置的储量是可以确定的),并移动左指针、更新左侧最大高度,反之计算并移动右指针。具体的代码如下所示。
public static int collectBeans(int[] height) {
int n = height.length;
int lh = height[0], rh = height[n - 1], l = 0, r = n - 1, ans = 0;
while (l < r) {
if (lh < rh) {
ans += lh - height[l];
l++;
lh = Math.max(lh, height[l]);
} else {
ans += rh - height[r];
r--;
rh = Math.max(rh, height[r]);
}
}
return ans;
}