基础知识
栈是一种常用的数据结构,它最大的特点是“后入先出”,即后进入栈中的元素最先出来。为了确保“后入先出”的顺序,栈的插入和删除操作都发生在栈顶。
如果碰到的问题是读入的数据暂时用不上的情形,通常数据会先保存到一个数据容器中以后再用。如果数据保存的顺序和使用顺序相反,那么最后保存的数据最先使用,这与栈的“后入先出”特性很契合,可以考虑将数据保存到栈中。
很多时候保存在栈中的数据是排序的。根据题目的不同,栈中的数据既可能是递增排序的,也可能是递减排序的。因此,有人将这种用排序的栈解决问题的方法称为单调栈法。
常见算法
一、小行星碰撞
题目:输入一个表示小行星的数组,数组中每个数字的绝对值表示小行星的大小,数字的正负号表示小行星运动的方向,正号表示向右飞行,负号表示向左飞行。如果两颗小行星相撞,那么体积较小的小行星将会爆炸最终消失,体积较大的小行星不受影响。如果相撞的两颗小行星大小相同,那么它们都会爆炸消失。飞行方向相同的小行星永远不会相撞。求最终剩下的小行星。例如,有6颗小行星[4,5,-6,4,8,-5],它们相撞之后最终剩下3颗小行星[-6,4,8]。
解题思路:
- 该题目符合“先入后出”的特点,先将数组中的数据入栈,每次入栈时,先判断自己如果是正数就直接入栈,如果是负数,而栈顶数字是正数,就可以比对大小,如果栈顶被碰撞掉,就出栈,继续比对下一个元素。直到结束。
Golang代码
func asteroidCollision(asteroids []int) []int {
list := make([]int, 0)
for _, item := range asteroids {
list = collide(list, item)
}
return list
}
func collide(list []int, val int) []int {
length := len(list)
for length > 0 {
if list[length-1] > 0 && val < 0 {
if list[length-1] > -val {
return list
} else if list[length-1] == -val {
list = list[:length-1]
return list
} else {
list = list[:length-1]
length--
}
} else {
list = append(list, val)
return list
}
}
if length == 0 {
list = append(list, val)
}
return list
}
二、每日温度
题目:输入一个数组,它的每个数字是某天的温度。请计算每天需要等几天才会出现更高的温度。例如,如果输入数组[35,31,33,36,34],那么输出为[3,1,1,0,0]。由于第1天的温度是35℃,要等3天才会出现更高的温度36℃,因此对应的输出为3。第4天的温度是36℃,后面没有更高的温度,它对应的输出是0。其他的以此类推。
解题思路:
- 用一个栈保存每天的温度在数组中的下标。每次从数组中读取一个温度,然后将其与栈中保存的温度(根据下标可以得到温度)进行比较。如果当前温度比位于栈顶的温度高,那么就能知道位于栈顶那一天需要等待几天才会出现更高的温度。然后出栈1次,将当前温度与下一个位于栈顶的温度进行比较。如果栈中已经没有比当前温度低的温度,则将当前温度在数组中的下标入栈。
Golang代码
func dailyTemperatures(temperatures []int) []int {
res := make([]int, len(temperatures))
list := make([]int, 0)
for i, item := range temperatures {
for {
if len(list) == 0 {
list = append(list, i)
break
} else {
if item > temperatures[list[len(list)-1]] {
res[list[len(list)-1]] = i - list[len(list)-1]
list = list[:len(list)-1]
} else {
list = append(list, i)
break
}
}
}
}
for _, item := range list {
res[item] = 0
}
return res
}
三、直方图最大矩形面积
题目:直方图是由排列在同一基线上的相邻柱子组成的图形。输入一个由非负数组成的数组,数组中的数字是直方图中柱子的高。求直方图中最大矩形面积。假设直方图中柱子的宽都为1。例如,输入数组[3,2,5,4,6,1,4,2],其对应的直方图如图所示,该直方图中最大矩形面积为12,如阴影部分所示。
解题思路:
-
解法一:暴力法(时间复杂度:logn2)
-
解法二:分治法(时间复杂度:nlogn)
-
解法三:单调栈法(时间复杂度:logn)
这种解法用一个栈保存直方图的柱子,并且栈中的柱子的高度是递增排序的。为了方便计算矩形的宽度,栈中保存的是柱子在数组中的下标,可以根据下标得到柱子的高度。这种解法的基本思想是确保保存在栈中的直方图的柱子的高度是递增排序的。假设从左到右逐一扫描数组中的每根柱子。如果当前柱子的高度大于位于栈顶的柱子的高度,那么将该柱子的下标入栈;否则,将位于栈顶的柱子的下标出栈,并且计算以位于栈顶的柱子为顶的最大矩形面积。
Golang代码
//分治法
func largestRectangleArea(heights []int) int {
return division(heights)
}
func division(list []int) int {
if len(list) == 0 {
return 0
}
var (
tag int
min = 1 << 31
)
for i, item := range list {
if item < min {
min = item
tag = i
}
}
current := min * len(list)
left := division(list[:tag])
right := division(list[tag+1:])
return getMax(current, left, right)
}
func getMax(numbers ...int) int {
tmp := 0
for _, item := range numbers {
if item > tmp {
tmp = item
}
}
return tmp
}
//单调栈法
func largestRectangleArea2(heights []int) int {
list := make([]int, 0)
var (
max = 0
)
for i, item := range heights {
for {
if len(list) > 0 && heights[list[len(list)-1]] >= item {
var w int
if len(list) == 1 {
w = i
} else {
w = i - list[len(list)-2] - 1
}
m := w * heights[list[len(list)-1]]
if m > max {
max = m
}
list = list[:len(list)-1]
} else {
list = append(list, i)
break
}
}
}
for len(list) > 0 {
w := 0
if len(list) == 1 {
w = len(heights)
} else {
w = len(heights) - 1 - list[len(list)-2]
}
m := heights[list[len(list)-1]] * w
if m > max {
max = m
}
list = list[:len(list)-1]
}
return max
}