代码随想录day51|503下一个更大元素II42接雨水|01笔记

108 阅读4分钟
  • 503下一个更大元素

  • 代码随想录 (programmercarl.com)
  • 第一印象

  • 这道题还是典型的可以利用单调栈的题目,本题与每日温度的区别就在于数组是可以循环的,我们需要为位置在后面的元素寻找前面位置的目标。由于我们只需要得到下一个更大的元素是多少即可,所以我们可以直接连接两个相同数组进行处理。然后再截取前面一半的结果集就可以。
  • 讲解观后感

  • 连接两个数组的方法是可行的,但是扩充数组的时候要多做一个O(n)的操作。我们其实可以直接在遍历过程中模拟两次遍历。
  • 使用的是取余的方法
  •      for i:=0;i<length*2;i++{
                for len(stack)>0&&nums[i%length]>nums[stack[len(stack)-1]]{
                    *
                    *
                    *
                }
    
  • 解题代码

  •     func nextGreaterElements(nums []int) []int {
            length := len(nums)
            result := make([]int,length,length)
            for i:=0;i<len(result);i++{
                result[i] = -1
            }
            //单调递减,存储数组下标索引
            stack := make([]int,0)
            for i:=0;i<length*2;i++{
                for len(stack)>0&&nums[i%length]>nums[stack[len(stack)-1]]{
                    index := stack[len(stack)-1]
                    stack = stack[:len(stack)-1] // pop
                    result[index] = nums[i%length]
                }
                stack = append(stack,i%length)
            }
            return result
        }
        
    
  • 42接雨水

  • 代码随想录 (programmercarl.com)
  • 讲解观后感

  • 接雨水这道题目是十分经典的面试题了。可以运用的方法有双指针法、动态规划、单调栈。
  • 不论是哪种方法,我们都是按照统计每行或者每列的方法来计算。我们以计算每列的情况来讨论。按照每列来看,那么我们就需要计算每列的雨水的高度。而决定每列高度的就是这列的两边分别存在的最高的柱子来决定,这两个柱子较小的一个决定了当前列的雨水的高度。

image.png

  • 用暴力的方法就是遍历每个位置(不包括第一个和最后一个,因为它们没有两边的柱子)时,再分别找到其两边最高的柱子,进行比较。这种暴力算法在力扣更新后已经无法ac了。为了得到两边的最高高度,使用了双指针来遍历,每到一个柱子都向两边遍历一遍,这其实是有重复计算的。我们把每一个位置的左边最高高度记录在一个数组上(maxLeft),右边最高高度记录在一个数组上(maxRight),这样就避免了重复计算。
    • 即从左向右遍历:maxLeft[i] = max(height[i], maxLeft[i - 1])
    • 从右向左遍历:maxRight[i] = max(height[i], maxRight[i + 1])
  • 单调栈解法

  • 单调栈的解法是按照行来计算的

image.png

  • 我们的单调栈要存储什么值呢?我们存储的依旧是数组下标。通过数组下标我们可以直接得到柱子高度并且可以计算出宽度(下标差)
  • 单调栈内的元素的顺我们选择从栈头到栈底增大的。这样当出现大于当前栈顶元素的时候我们能做弹出操作。
  • 单调栈的逻辑主要就是三种情况
  • 情况一:当前遍历的元素(柱子)高度小于栈顶元素的高度 height[i] < height[st.top()]
    • 如果当前遍历的元素(柱子)高度小于栈顶元素的高度,就把这个元素加入栈中,因为栈里本来就要保持从小到大的顺序(从栈头到栈底)。
  • 情况二:当前遍历的元素(柱子)高度等于栈顶元素的高度 height[i] == height[st.top()]
    • 如果当前遍历的元素(柱子)高度等于栈顶元素的高度,要跟更新栈顶元素,因为遇到相相同高度的柱子,需要使用最右边的柱子来计算宽度。

image.png

  • 情况三:当前遍历的元素(柱子)高度大于栈顶元素的高度 height[i] > height[st.top()]

image.png - 取栈顶元素,将栈顶元素弹出,这个就是凹槽的底部,也就是中间位置,下标记为mid,对应的高度为height[mid](就是图中的高度1)。
- 此时的栈顶元素st.top(),就是凹槽的左边位置,下标为st.top(),对应的高度为height[st.top()](就是图中的高度2)。
- 当前遍历的元素i,就是凹槽右边的位置,下标为i,对应的高度为height[i](就是图中的高度3)。
- 此时大家应该可以发现其实就是栈顶和栈顶的下一个元素以及要入栈的元素,三个元素来接水!
那么雨水高度是 min(凹槽左边高度, 凹槽右边高度) - 凹槽底部高度,代码为: int h = min(height[st.top()], height[i]) - height[mid];
雨水的宽度是 凹槽右边的下标 - 凹槽左边的下标 - 1(因为只求中间宽度),代码为: int w = i - st.top() - 1 ;
- 当前凹槽雨水的体积就是: h * w

  • 注意我们初始化的时候要将下标0的柱子压入栈。
  • 解题代码

  • 单调栈
  •     func trap(height []int) int {
           if len(height) <= 2 {
              return 0
           }
           st := make([]int, 1, len(height)) // 切片模拟单调栈,st存储的是高度数组下标
           var res int
           for i := 1; i < len(height); i++ {
              if height[i] < height[st[len(st)-1]] {
                 st = append(st, i)
              } else if height[i] == height[st[len(st)-1]] {
                 st = st[:len(st)-1] // 比较的新元素和栈顶的元素相等,去掉栈中的,入栈新元素下标
                 st = append(st, i)
              } else {
                 for len(st) != 0 && height[i] > height[st[len(st)-1]] {
                    top := st[len(st)-1]
                    st = st[:len(st)-1]
                    if len(st) != 0 {
                       tmp := (min(height[i], height[st[len(st)-1]]) - height[top]) * (i - st[len(st)-1] - 1)
                       res += tmp
                    }
                 }
                 st = append(st, i)
              }
           }
           return res
        }
        
        
        func min(x, y int) int {
           if x >= y {
              return y
           }
           return x
        }
    
  • 双指针
  •     func trap(height []int) int {
            sum:=0
            n:=len(height)
            lh:=make([]int,n)
            rh:=make([]int,n)
            lh[0]=height[0]
            rh[n-1]=height[n-1]
            for i:=1;i<n;i++{
                lh[i]=max(lh[i-1],height[i])
            }
            for i:=n-2;i>=0;i--{
                rh[i]=max(rh[i+1],height[i])
            }
            for i:=1;i<n-1;i++{
                h:=min(rh[i],lh[i])-height[i]
                if h>0{
                    sum+=h
                }
            }
            return sum
        }
        func max(a,b int)int{
            if a>b{
                return a
            }
            return b
        }
        func min(a,b int)int{
            if a<b{
                return a
            }
            return b
        }
    
  • 双指针精简版(计算每列)
  •     func trap(height []int) int {
        	var left, right, leftMax, rightMax, res int
        	right = len(height) - 1
        	for left < right {
        		if height[left] < height[right] {
        			if height[left] >= leftMax {
        				leftMax = height[left]  // 设置左边最高柱子
        			} else {
        				res += leftMax - height[left]  // //右边必定有柱子挡水,所以遇到所有值小于等于leftMax的,全部加入水池中
        			}
        			left++
        		} else {
        			if height[right] > rightMax {
        				rightMax = height[right]  // //设置右边最高柱子
        			} else {
        				res += rightMax - height[right]  // //左边必定有柱子挡水,所以,遇到所有值小于等于rightMax的,全部加入水池
        			}
        			right--
        		}
        	}
        	return res
        }