仔细读题,按照题目说的,最直接的想法:接到雨水的话应该需要 2 个边界,左边界和右边界,那么用 i 循环左边界,j 找右边界,雨水个数就是左边界和右边界之间的间隔 * min(左边界,右边界) - 左边界到右边界之间的黑色柱子,你这么一听就应该能想到时间复杂度挺高的,但我们本着先用简单正确的思路解决问题,如果提交超时了那么在思考如何优化。所以这里先不计较时间复杂度,那么可以写出如下代码:
function trap(height: number[]): number {
const len=height.length
// 计算(i,j)的雨水个数,即矩形面积减去黑色柱子之和
const getRange=(i,j,base)=>{
let sum=0
for(let k=i+1;k<j;k++) sum+=height[k]
return base*(j-i-1)-sum
}
let res=0
for(let i=0;i<len;i++){
// 当前第i个柱子是否最大
let isCurrentMax=true
let rightMax=0,rightMaxIdx=-1
for(let j=i+1;j<len;j++){
// 如果右边有比当前柱子大的,那么矩形的高度是当前柱子的高度,矩形的宽度就是 j-i-1
if(height[j]>=height[i]){
res+=getRange(i,j,height[i])
// 如果找到答案,应该忽略中间,防止重复计算
i=j-1
isCurrentMax=false
// 如果找到了,那么就应该退出循环
break
}
// 维护右边最大值和坐标
if(rightMax<height[j]){
rightMax=height[j]
rightMaxIdx=j
}
}
// 如果右边没有比当前即i柱子大的,那么当前i柱子就是最大的
// 所以矩形的高应该是右边最大的柱子
if(isCurrentMax&&rightMaxIdx!==-1){
res+=getRange(i,rightMaxIdx,height[rightMaxIdx])
// 如果找到答案,应该忽略中间,防止重复计算
i=rightMaxIdx-1
}
}
return res
};
这应该是一个比较直接的解法,我想着错误答案应该是不会的,但应该会超时,然而意想不到的是居然通过了 😂
那么我们来看下时间复杂度,最差情况应该是 O(n^3),非常高,按理来说题目数据量1 <= n <= 2 * 10^4,这个数据量 O(n^2) 的算法应该都会超时的,更何况 O(n^3)了,但能到最差情况应该也很少。
当然作为高级的程序员,你应该去继续优化算法。
getRange 可用前缀和优化,降到 O(1)。右边的最值也可预先处理,降到降到 O(1)。但是需要解决个问题,就是找到当前元素间隔最近的比他大的元素的下标,这个我想了半天,只想到了 O(n^2)的解决方案,没有想到更快的了。但是看了题解后,发现这里可以用单调栈来解决,单调栈能在 O(n)复杂度内找到离他最近的比他大的或小的值。
class Stack{
list:number[][]
constructor(){
this.list=[]
}
push(val){
this.list.push(val)
}
pop(){
return this.list.pop()
}
top(){
return this.list[this.list.length-1]
}
isEmpty(){
return !this.list.length
}
clear(){
this.list.length=0
}
}
function trap(height: number[]): number {
const len=height.length
const prefixSum:number[]=[]
let preSum=0
height.forEach(i=>{
preSum+=i
prefixSum.push(preSum)
})
// 计算(i,j)的雨水个数,即矩形面积减去黑色柱子之和
const getRange=(i:number,j:number,base:number)=>{
return base*(j-i-1)-(prefixSum[j-1]-prefixSum[i])
}
let rNextMax:number[][]=[]
let sc:Stack=new Stack()
for(let i=len-1;i>=0;i--){
while(!sc.isEmpty()&&sc.top()[0]<=height[i]){
sc.pop()
}
if(sc.isEmpty()){
rNextMax[i]=[-1,-1]
}else {
rNextMax[i]=sc.top()
}
sc.push([height[i],i])
}
let rRangeMax:number[][]=[]
let t=0,tidx=len-1
for(let i=len-1;i>=0;i--){
if(t<height[i]){
t=height[i]
tidx=i
}
rRangeMax[i]=[t,tidx]
}
let res =0
for(let i=0;i<len;i++){
if(rNextMax[i][1]>i){
res+=getRange(i,rNextMax[i][1],height[i])
i=rNextMax[i][1]-1
}else {
if(i+1===len) continue
res+=getRange(i,rRangeMax[i+1][1],rRangeMax[i+1][0])
i=rRangeMax[i+1][1]-1
}
}
return res
};
(说实话,这种方法要比后面的方法麻烦的多,但是毕竟是自己想出来的,比较偏爱)
还可以用另一种思路,比较难想,但简单,也不需要使用单调栈数据结构,可以背下来:只看局部,对于第 i 个位置能装多少水?取决于min(左边最大值,右边最大值),最值可以预先处理,所以时间复杂度是 O(n)
function trap(height: number[]): number {
let n=height.length
let t=0
let rMax:number[]=[]
for(let i=n-1;i>=0;i--){
if(height[i]>t){
t=height[i]
}
rMax[i]=t
}
t=0
let res=0
for(let i=0;i<n;i++){
if(height[i]>t){
t=height[i]
}
res+=Math.min(t,rMax[i])-height[i]
}
return res
};