柱状图中最大的矩形

题目描述

给定 n 个非负整数,用来表示柱状图中各个柱子的高度。每个柱子彼此相邻,且宽度为 1 。

求在该柱状图中,能够勾勒出来的矩形的最大面积。

柱状图的示例,其中每个柱子的宽度为 1,给定的高度为 [2,1,5,6,2,3]。

图中阴影部分为所能勾勒出的最大矩形面积,其面积为 10 个单位。

来源:力扣(LeetCode) 链接:https://leetcode-cn.com/problems/largest-rectangle-in-histogram 著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。

思路

将题目抽象成对于一个高度h,对其向左右延伸,延伸时需要保证其他高度必须大于h,延伸后的长度就是该位置上的高度h所能形成的矩形的宽度。

例如对于第一个柱子高度为2,左延伸为0,右延伸为0,因为1<2,则其宽度为1,则该位形成的矩形面积为2。

注意这里不能降低高度去延伸,必须使用高度2去延伸。

因此,可以进一步将问题抽象成,对于一个数h,在数组中向左右分别找到第一个小于它的数,这之间的距离就是宽度。

因此考虑使用单调栈来解决。

https://oi-wiki.org/ds/monotonous-stack/

对于样例,[2,1,5,6,2,3]

维护一个单调增栈,向右找到第一个小于它的数,然后记录两个数之间的距离,即下标相减

  • 初始栈空,2入栈,此时栈[2]
  • 1尝试入栈,此时栈顶2>1,则2出栈,因为1入栈导致了2出栈,则表示1是第一个小于它的数,则记录下2的距离为2-1=1,此时栈[1]
  • 5入栈,6入栈,此时栈[1,5,6]
  • 2尝试入栈,6>2,6出栈,2是第一个小于6的数,记录6的距离为5-4=1
  • 2尝试入栈,5>2,5出栈,2是第一个小于5的数,记录5的距离为5-3=2
  • 2入栈,此时栈[1,2]
  • 3入栈,此时栈[1,2,3]
  • 对于还在栈内的元素,其距离都为数组长度6减去下标位置
  • 最终向右延伸的长度数组为[1,5,2,1,2,1]

然后对该数组翻转,再用上述方法求一次,则是向左延伸的长度[1,2,1,1,3,1]

将两个数组求和,去掉重复的当前位置,则长度数组为[1,6,2,1,4,1]

再用长度数组和高度数组求乘积,得到面积数组[2,6,10,6,8,3],该数组的最大值即为答案

code

type node struct {
	idx, val int
}

func largestRectangleArea(heights []int) int {
	h := make([]node, len(heights))
	reh := make([]node, len(heights))
	for k, v := range heights {
		h[k] = node{k, v}
		reh[len(reh)-k-1] = node{len(reh) - k - 1, v}
	}
	stack1 := make([]node, 0, len(heights))
	stack2 := make([]node, 0, len(heights))
	stack1 = append(stack1, h[0])
	stack2 = append(stack2, reh[0])

	left := make([]int, len(heights))
	right := make([]int, len(heights))
	for i := 0; i < len(heights); i++ {
		left[i], right[i] = len(heights), len(heights)
	}

	for i := 1; i < len(heights); i++ {
		if h[i].val > stack1[len(stack1)-1].val {
			stack1 = append(stack1, node{i, h[i].val})
			goto re
		}
		for len(stack1) >= 1 && stack1[len(stack1)-1].val > h[i].val {
			right[stack1[len(stack1)-1].idx] = i
			stack1 = stack1[:len(stack1)-1]
		}
		stack1 = append(stack1, node{i, h[i].val})
	re:
		if reh[i].val > stack2[len(stack2)-1].val {
			stack2 = append(stack2, node{i, reh[i].val})
			continue
		}
		for len(stack2) >= 1 && stack2[len(stack2)-1].val > reh[i].val {
			left[stack2[len(stack2)-1].idx] = i
			stack2 = stack2[:len(stack2)-1]
		}
		stack2 = append(stack2, node{i, reh[i].val})
	}
	ans, lens := -1, len(heights)
	for i := 0; i < len(heights); i++ {
		//right[i] - i
		//left[lens-i-1] - lens + i +1
		//-1
		ans = max(ans, heights[i]*(right[i]-i+left[lens-i-1]-lens+i))
	}
	return ans
}

func max(a, b int) int {
	if a > b {
		return a
	}
	return b
}

单调栈的其他例题

下一个更大元素 I

给你两个 没有重复元素 的数组 nums1 和 nums2 ,其中nums1 是 nums2 的子集。

请你找出 nums1 中每个元素在 nums2 中的下一个比其大的值。

nums1 中数字 x 的下一个更大元素是指 x 在 nums2 中对应位置的右边的第一个比 x 大的元素。如果不存在,对应位置输出 -1 。

来源:力扣(LeetCode) 链接:https://leetcode-cn.com/problems/next-greater-element-i 著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。

思路

单调栈对nums2数组求一次即可

code

func nextGreaterElement(nums1 []int, nums2 []int) []int {
	hash := make(map[int]int)
	stack := make([]int, 0, len(nums2))
	stack = append(stack, nums2[0])
	for i := 1; i < len(nums2); i++ {
		if nums2[i] < stack[len(stack)-1] {
			stack = append(stack, nums2[i])
            continue
		}
		for len(stack) >= 1 && nums2[i] > stack[len(stack)-1] {
			hash[stack[len(stack)-1]] = nums2[i]
			stack = stack[:len(stack)-1]
		}
		stack = append(stack, nums2[i])
	}
	ans := make([]int, len(nums1))
	for i := 0; i < len(nums1); i++ {
		if v, ok := hash[nums1[i]]; ok {
			ans[i] = v
		} else {
			ans[i] = -1
		}
	}
	return ans
}

每日温度

请根据每日 气温 列表,重新生成一个列表。对应位置的输出为:要想观测到更高的气温,至少需要等待的天数。如果气温在这之后都不会升高,请在该位置用 0 来代替。

例如,给定一个列表 temperatures = [73, 74, 75, 71, 69, 72, 76, 73],你的输出应该是 [1, 1, 4, 2, 1, 1, 0, 0]。

提示:气温 列表长度的范围是 [1, 30000]。每个气温的值的均为华氏度,都是在 [30, 100] 范围内的整数。

来源:力扣(LeetCode) 链接:https://leetcode-cn.com/problems/daily-temperatures 著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。

思路

单调栈对温度列表求一次即可,在入栈出栈的时候记录下标位置用于算距离即可,与柱状图矩形类似

code

type node struct {
	idx, val int
}

func dailyTemperatures(T []int) []int {
	arr := make([]node, len(T))
	for k, v := range T {
		arr = append(arr, node{k, v})
	}
	stack := make([]node, 0, len(T))
	ans := make([]int, len(T))
	stack = append(stack, arr[0])
	for i := 1; i < len(arr); i++ {
		if arr[i].val < stack[len(stack)-1].val {
			stack = append(stack, arr[i])
			continue
		}
		for len(stack) >= 1 && arr[i].val > stack[len(stack)-1].val {
			ans[stack[len(stack)-1].idx] = arr[i].idx - stack[len(stack)-1].idx
			stack = stack[:len(stack)-1]
		}
		stack = append(stack, arr[i])
	}
	return ans
}

下一个更大元素 II

给定一个循环数组(最后一个元素的下一个元素是数组的第一个元素),输出每个元素的下一个更大元素。数字 x 的下一个更大的元素是按数组遍历顺序,这个数字之后的第一个比它更大的数,这意味着你应该循环地搜索它的下一个更大的数。如果不存在,则输出 -1。

输入: [1,2,1]
输出: [2,-1,2]
解释: 第一个 1 的下一个更大的数是 2;
数字 2 找不到下一个更大的数; 
第二个 1 的下一个最大的数需要循环搜索,结果也是 2。

来源:力扣(LeetCode) 链接:https://leetcode-cn.com/problems/next-greater-element-ii 著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。

思路

对于环形数组,则将数组复制多一份即可[1,2,1]–>[1,2,1,1,2,1]

这样就能求到数h后面的和前面的数

这里在实际写的时候,可以不用复制一份,可以用取模的方法模拟复制,节省一份内存

code

type node struct {
	idx, val int
}

func nextGreaterElements(nums []int) []int {
    if len(nums) == 0 {
		return []int{}
	}
	stack := make([]node, 0, len(nums))
	ans := make([]int, len(nums))
	for k := range ans {
		ans[k] = -1
	}
	stack = append(stack, node{0, nums[0]})
	for i := 1; i < len(nums)*2; i++ {
		j := i % len(nums)
		if nums[j] < stack[len(stack)-1].val {
			stack = append(stack, node{j, nums[j]})
			continue
		}
		for len(stack) >=1 && nums[j] > stack[len(stack)-1].val {
			ans[stack[len(stack)-1].idx] = nums[j]
			stack = stack[:len(stack)-1]
		}
		stack = append(stack, node{j, nums[j]})
	}
	return ans
}