直方图的水量--木桶原理


引言

力扣题目【直方图的水量】描述 -> 传送门
所用语言:Java
执行用时 1 ms,内存消耗 38.1 MB


正文

昨天和前天的题解由于时间比较匆忙,没有来得及写的很详细 😣(不过也没有多少人看就是了)
今天是假期的第一天,时间比较充裕,我尽量将本篇题解写的详细一点,也对得起这道困难题。

我该怎么和你说清楚我的思路呢?🤔

👇这些是废话,要是时间紧迫,可直接跳过
我不喜欢我的思路被别人牵着走。
力扣的题目有时会有一些折叠起来的提示,我不喜欢去看这些提示。或者说在我觉得我的思路还没有真正走到死路之前,我是不会去看提示的。
要是看了提示,我的思路常常会被固化,会被那些提示牵着走,虽然说最终解出题目的概率会增大,但是或许我自己一开始的思路比提示的思路是更优解呢?谁也说不准。

在解出这道题时,我还没有看过力扣给的题目提示,所以,我会完全按照我自己的思路来讲解这道题。

一开始看到这道题,我想到了什么?

在这里插入图片描述
① 木桶原理[短板理论]一个木桶存水量的多少,不取决与最长的木片的长度,而是取决于最短的木片的长度(以前的木桶是用多个木片围起来而形成的)。这道题也是存水问题,仔细看一下上面的直方图,就更能理解 [取决于最短的木片的长度] 的含义了(图中红色部分为可存水的部分)
② 高度大于 0 的柱子才能做为“木桶的木片”:高度为 0 的柱子是兜不住水的,例如 height[0]、height[2]
③ 存水量的多少取决于高度差

OK,开始解题
  • 按照木桶原理,我可以先选中一个柱子 a,然后再找出另外一根柱子 b,要求 b 的高度大于等于 a,即 height[b] >= height[a]。这样的话,a 和 b 之前就能形成一个凹的区域来存水
  • a 和 b 之前存水量的多少取决于 a 的高度.假设 a 和 b 之前还存在 c、d 柱子(当然,height[c] < height[a]height[d] < height[a]),则 a 和 b 之前的存水量 = (height[a] - height[c]) + (height[a] - height[d]),即 a 与它们的高度差之和
  • “桶”的寻找要有方向性以保证不会计算到重复的水量。例如,我从左到右计算水量,那么我先选中一个柱子 a,向右找到了另外一个符合条件的柱子 b,计算出它们之前的存水量;则下次以 b 为“柱子 a”,向右继续寻找符合条件(height[x] >= height[b])的柱子

下面我给出以这种思路而写出的代码:

public int trap(int[] height) {
    if (height == null) {
        return 0;
    }

    int result = 0;
    // 从左往右求水量
    for (int i = 0; i < height.length; ) {
        if (height[i] != 0) {
            int j = i + 1;
            int waterVolume = 0;
            // 往右寻找高度大于等于height[i]的柱子,并记录下沿途柱子与height[i]的高度差之和
            while (j < height.length && height[j] < height[i]) {
                waterVolume += (height[i] - height[j]);
                j++;
            }

			if (j == height.length) {
				// 说明从i开始往右的区域不能形成height[i]为最短木板的桶,兜不住水
				i++;
			} else {
			    result += waterVolume;
			    // 寻找下一个桶
            	i = j;	
			}
        } else {
            // 跳过左边界高度为0的柱子
            i++;
        }
    }
    
    return result;
}

okk,直到现在我发现我的思路还是没啥问题的,我在力扣上也写了两个测试用例来测试上面的代码,发现没问题。
但是,但是,这并不是十分正确的答案! 上面的代码失败在了下面的测试用例中:
在这里插入图片描述
很明显,这个直方图的存水量是 1,但是上面的代码的输出结果是 0

真正正确的答案

因为考虑不全,导致提交了一次失败的提交[又拉低了提交通过率,心痛]
失败的原因在于,没有考虑到最高柱子的存在,老是把重点放在了“短木片”上面了。因为你如果选中最高的柱子做为起始柱子,那么你向右寻找,不管怎么找都不会找到符合条件的另一个柱子(除非有多个最高柱子)。
所以,我们不如转变一下寻找的方向,不再是从左到右寻找或者从右到左寻找,而是从两端向最高的柱子聚拢寻找。更改后的代码如下所示:

class Solution {

    public int trap(int[] height) {
        if (height == null) {
            return 0;
        }

        int result = 0;
        int highest = findHighest(height);
        // 从左往右求水量
        for (int i = 0; i < highest; ) {
            if (height[i] != 0) {
                int j = i + 1;
                int waterVolume = 0;
                // 往右寻找高度大于等于height[i]的柱子,并记录下沿途柱子与height[i]的高度差之和
                while (j < highest && height[j] < height[i]) {
                    waterVolume += (height[i] - height[j]);
                    j++;
                }

                result += waterVolume;
                i = j;
            } else {
                // 跳过左边界的左边高度为0的柱子
                i++;
            }
        }

        // 从右往左求水量
        for (int i = height.length - 1; i > highest; ) {
            if (height[i] != 0) {
                int j = i - 1;
                int waterVolume = 0;
                // 往右寻找高度大于等于height[i]的柱子,并记录下沿途柱子与height[i]的高度差之和
                while (j > highest && height[j] < height[i]) {
                    waterVolume += (height[i] - height[j]);
                    j--;
                }

                result += waterVolume;
                i = j;
            } else {
                // 跳过右边界的右边高度为0的柱子
                i--;
            }
        }
        return result;
    }

    // 找出最高的柱子的下标
    private int findHighest(int[] height) {
        int index = 0;
        for (int i = 1; i < height.length; i++) {
            if (height[i] > height[index]) {
                index = i;
            }
        }
        return index;
    }
}

这是正确的答案,各位可看一看


最后

解完题之后,我看了一下力扣给出的提示,其中第 1 个提示就说到:直方图中最高的长方形起什么作用?如果我要是一开始就看到了提示,或许就能避免那次失败提交了。但是,凭自己的思路找到的正确答案,不更香吗?
这道题虽说是困难题,但是因为力扣给出了直观的图形给我们观察,难度降低了不少。
最后还是希望大家多多 AC 🤗

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值