文章目录
q20 有效的括号
题解
遍历字符串,使用栈来保存左括号,遇到右括号时,与栈顶元素作比较即可。
func isValid(s string) bool {
var stack []byte
for i := 0; i < len(s); i++ {
if s[i] == '(' || s[i] == '[' || s[i] == '{' {
stack = append(stack, s[i])
}
if len(stack) == 0 {
return false
}
if s[i] == ')' {
if stack[len(stack) - 1] == '(' {
stack = stack[:len(stack) - 1]
} else {
return false
}
}
if s[i] == ']' {
if stack[len(stack) - 1] == '[' {
stack = stack[:len(stack) - 1]
} else {
return false
}
}
if s[i] == '}' {
if stack[len(stack) - 1] == '{' {
stack = stack[:len(stack) - 1]
} else {
return false
}
}
}
if len(stack) != 0 {
return false
}
return true
}
q32 最长有效括号
题解
这道题可以用栈来解决,采用以下策略:
1.栈内存放括号的位置(下标)。
2.栈基存放的是最后一个多余右括号的位置,方便计数,初始时压入-1(每次我们都会先将栈顶元素弹出,栈基元素可以帮助我们对最长的那个括号对长度计数)。
3.遇到右括号时必须先弹出一个值,然后判断:
- 如果弹出后栈内元素为空(说明新加入的这个元素是多出来的右括号),那么更新最后一个多余右括号的位置(将这个多出来的右括号的下标保存到栈基中)。
- 如果不为空,那就更新最长有效括号的长度,方法是当前下标减去栈顶元素的下标。
func longestValidParentheses(s string) (maxLength int) {
stack := []int{-1}
for i := 0; i < len(s); i++ {
// 如果是左括号,直接入栈
if s[i] == '(' {
stack = append(stack, i)
} else {
// 否则不管是什么,首先出栈一个元素
stack = stack[:len(stack) - 1]
// 如果栈此时为空了,说明弹出的是栈基元素
// 说明新遍历到的这个元素是多余的右括号
// 所以要将这个新的右括号的下标存到栈基中
if len(stack) == 0 {
stack = append(stack, i)
} else {
// 右括号,但是被栈中的左括号中和了,更新结果值即可
if maxLength < i - stack[len(stack) - 1] {
maxLength = i - stack[len(stack) - 1]
}
}
}
}
return
}
q84 柱形图中最大的矩形
题解
这道题可以使用暴力法来解决,就是每次以当前下标为中心点,向两边找左右边界,同时更新最大面积.
func largestRectangleArea(heights []int) int {
area := 0
for i := 0; i < len(heights); i++ {
w := 1
h := heights[i]
j := i - 1
for j >= 0 && heights[j] >= h {
w++
j--
}
j = i + 1
for j < len(heights) && heights[j] >= h {
w++
j++
}
area = max(area, w*h)
}
return area
}
func max(a, b int) int {
if a > b {
return a
} else {
return b
}
}
复杂度为O(n^2)
,会超时.
上述解法在寻找左右边界的时候复杂度是O(n)
,所以我们要考虑怎么把这部分的复杂度优化为O(1)
,可以使用单调栈来实现,栈中的元素单调递增,栈中每一个元素的左边界都是它的底下一个元素.
这个时候新遍历到一个元素,如果这个元素大于栈顶元素,那么直接入栈,而如果这个元素小于栈顶元素,那么这个元素其实就是栈顶元素的右边界,这个时候,将栈顶元素弹出,同时更新最大面积,接着让这个元素继续与新的栈顶元素做比较,直到这个元素大于栈顶元素,就将它放到栈中.
另外为了减少代码量,在初始化的时候,我们需要将0
放到heights[i]的末尾,作为最后一个元素的右边界,然后将-1
放到栈的栈底,确保第一个元素与栈中元素作比较的时候能顺利入栈.
func largestRectangleArea(heights []int) (maxArea int) {
stack := make([]int, 0)
// 初始化栈,将-1压入栈基,当作第一个元素的左边界
stack = append(stack, -1)
// 相应的heights也要增加一个元素,与stack对应
heights = append(heights, 0)
// 开始遍历
for i := 0; i < len(heights); i++ {
// 如果当前元素小于栈顶元素,那么当前元素就是栈顶元素的右边界
for len(stack) > 1 && heights[stack[len(stack) - 1]] > heights[i] {
// 备份栈顶元素
top := heights[stack[len(stack) - 1]]
// 弹出栈顶元素
stack = stack[:len(stack) - 1]
// 获取栈顶元素的左边界值
left := stack[len(stack) - 1]
// 更新最大值
maxArea = max(maxArea, (i - left - 1) * top)
}
// 当前元素比栈顶元素大,就压入栈中,原来的栈顶元素就是当前元素的左边界
stack = append(stack, i)
}
return
}
func max(a, b int) int {
if a > b {
return a
} else {
return b
}
}
q155 最小栈
题解
这道题的要求是要以常数时间复杂度来找最小值,我使用一个辅助栈来解决。
MinStack结构体中有两个栈,一个主栈,用来存放元素,一个辅助栈,用来存放最小值,辅助栈的栈顶用来储存当前的最小值。
其中辅助栈每次压栈的是当前主栈和新压入元素之间的最小值。这样就可以保证,如果主栈的最小值在栈顶,弹栈后,辅助栈也相应弹栈,这时候辅助栈的栈顶仍然是当前所有元素的最小值。
如图所示:
type MinStack struct {
stack []int
minStack []int
}
func Constructor() MinStack {
return MinStack{
stack: make([]int, 0),
minStack: []int{math.MaxInt32},
}
}
func (this *MinStack) Push(val int) {
this.stack = append(this.stack, val)
if val < this.minStack[len(this.minStack) - 1] {
this.minStack = append(this.minStack, val)
} else {
this.minStack = append(this.minStack, this.minStack[len(this.minStack) - 1])
}
}
func (this *MinStack) Pop() {
this.stack = this.stack[:len(this.stack) - 1]
this.minStack = this.minStack[:len(this.minStack) - 1]
}
func (this *MinStack) Top() int {
return this.stack[len(this.stack) - 1]
}
func (this *MinStack) GetMin() int {
return this.minStack[len(this.minStack) - 1]
}
q224 基本计算器
题解
这道题只有加法和减法,只需要定义一个栈即可。res用于保存最终结果,sign用来标记上一次出现的符号,num用来储存数字。
然后遍历字符串即可,如果是数字就储存在num中,如果是+号或者-号就先累加前面的数字:res += num * sign,接着将sign置为1或者-1,然后num置为0 即可。如果是左括号,那就先将计算结果和括号前的符号压入栈中,然后将res置为0,用于保存括号中的运算结果。当出现右括号时,就将栈中保存的结果与括号里面的计算结果res进行整合。
func calculate(s string) int {
stack, res, num, sign := []int{}, 0, 0, 1
for i := 0; i < len(stack); i++ {
if s[i] >= '0' && s[i] <= '9' {
num = num * 10 + int(s[i] - '0')
} else if s[i] == '+' {
res += sign * num
num = 0
sign = 1
} else if s[i] == '-' {
res += sign * num
num = 0
sign = -1
} else if s[i] == '(' {
// 将前一个结果和符号压入栈中
stack = append(stack, res)
stack = append(stack, sign)
// 将结果置为0, 只需要在括号内计算新结果即可
res = 0
sign = 1
} else if s[i] == ')' {
res += sign * num
num = 0
res *= stack[len(stack) - 1]
res += stack[len(stack) - 2]
stack = stack[:len(stack) - 2]
}
}
// 最后不是以右括号结尾就是以一个数字结尾
if num != 0 {
res += sign * num
}
return res
}
q232 用栈实现队列
题解
题目提供了两个栈,一个作为入队栈stack1,每次入队时压入stack1;一个作为出队栈stack2,每次要出队时,先判断stack2是否为空,如果为空,就将stack1中的所有元素都压入stack2中,然后弹出栈顶元素;如果不为空,直接弹出栈顶元素即可。刚刚好可以实现队列先入先出的特性。
返回队头元素同理。
如果两个栈同时为空,那么队列就为空。
type MyQueue struct {
stack1, stack2 []int
}
func Constructor() MyQueue {
return MyQueue{}
}
func (this *MyQueue) Push(x int) {
this.stack1 = append(this.stack1, x)
}
func (this *MyQueue) Pop() int {
if len(this.stack2) == 0 {
for len(this.stack1) != 0 {
this.stack2 = append(this.stack2, this.stack1[len(this.stack1) - 1])
this.stack1 = this.stack1[:len(this.stack1) - 1]
}
}
front := this.stack2[len(this.stack2) - 1]
this.stack2 = this.stack2[:len(this.stack2) - 1]
return front
}
func (this *MyQueue) Peek() int {
if len(this.stack2) == 0 {
for len(this.stack1) != 0 {
this.stack2 = append(this.stack2, this.stack1[len(this.stack1) - 1])
this.stack1 = this.stack1[:len(this.stack1) - 1]
}
}
front := this.stack2[len(this.stack2) - 1]
return front
}
func (this *MyQueue) Empty() bool {
if len(this.stack1) == 0 && len(this.stack2) == 0 {
return true
} else {
return false
}
}
q316 去除重复字母
题解
首先遍历字符串,记录每个字母出现的次数。然后再次遍历字符串,exist数组用来标记栈中是否已经存在当前字母,如果存在,就跳过。如果不存在则循环与栈顶字母作比较,如果栈顶字母大于当前字母同时后面还存在栈顶元素,就弹出栈顶元素。最后将字母入栈。
func removeDuplicateLetters(s string) string {
var count [26]int
var exist [26]bool
var stack []rune
for _, v := range s {
count[v - 'a']++
}
for _, v := range s {
if exist[v - 'a'] {
count[v - 'a']--
continue
}
// 达到条件应该出栈
// 1.栈里有元素
// 2.栈顶元素大于当前元素
// 3.栈顶元素在后续出现过
for len(stack) > 0 && stack[len(stack) - 1] > v && count[stack[len(stack) - 1] - 'a'] > 0 {
exist[stack[len(stack) - 1] - 'a'] = false
stack = stack[:len(stack) - 1]
}
stack = append(stack, v)
count[v - 'a']--
exist[v - 'a'] = true
}
return string(stack)
}
q394 字符串解码
题解
这道题的难点在于括号的嵌套,多层括号就很难保存每一层括号的状态。这里我们使用双栈来保存临时状态.
一个栈用来保存每个中括号前面的数字,另一个栈用来保存在进入下一个中括号之前,上一个中括号中的字符串.
首先遍历字符串,字符串中出现的字符有四种类型,我们可以分别来考虑:
(1)出现的是数字,我们将它转化为整形并保存在multi
变量中.
(2)出现的是字母,将它累加在res字符串的后面.
(3)出现的是[
括号,将当前的res和multi分别入栈,并置为空和0,为了进入新的[
中重新进行计数.
(4)出现的是]
括号,将栈顶元素出栈,计算 res = last_res + cur_multi * res
.其中last_res
是栈顶元素,cur_multi
也是栈顶元素.比如遍历到了一个字符串:3[a2[b]]
,last_res
就是a,cur_multi
就是2.
注意每次遍历到]
括号的时候,要将两个栈的栈顶元素弹出.
func decodeString(s string) string {
var res string
multi := 0
stackMulti := make([]int, 0)
stackRes := make([]string, 0)
for _, v := range s {
if v == '[' {
stackMulti = append(stackMulti, multi)
stackRes = append(stackRes, res)
multi = 0
res = ""
} else if v == ']' {
var tmp string
curMulti := stackMulti[len(stackMulti)-1]
stackMulti = stackMulti[:len(stackMulti)-1]
for i := 0; i < curMulti; i++ {
tmp += res
}
res = stackRes[len(stackRes)-1] + tmp
stackRes = stackRes[:len(stackRes)-1]
} else if v >= '0' && v <= '9' {
multi = multi*10 + int(v-'0')
} else {
res += string(v)
}
}
return res
}
q739 每日温度
题解
可以使用单调栈求解,正向遍历温度列表,将还没有找到温度更高那天的元素入栈,每次将遍历到的元素与栈顶元素作比较,如果当前遍历到的元素更大,就为栈顶元素赋值,然后弹出栈顶元素,最后将当前元素入栈(其实每一次最后都需要把当前遍历到的元素入栈)。
func dailyTemperatures(temperatures []int) (res []int) {
n := len(temperatures)
stack := make([]int, 0)
res = make([]int, n)
for i := 0; i < n; i++ {
for len(stack) > 0 && temperatures[i] > temperatures[stack[len(stack) - 1]] {
// 为栈顶元素赋值
res[stack[len(stack) - 1]] = i - stack[len(stack) - 1]
// 弹出栈顶元素
stack = stack[:len(stack) - 1]
}
// 栈中已经没有比当前温度高的温度, 将当前温度入栈
stack = append(stack, i)
}
return
}
剑指 Offer 09. 用两个栈实现队列
题解
使用切片来表示栈,我们可以只使用一个栈来作为队列,另外一个栈当作辅助栈。元素入队时,将元素放入stack1中,元素出队时,先判断stack2中有没有元素,有元素直接从stack2中弹出元素,没有元素就将stack1中的元素全部放入stack2中再出队。
入队操作比较简单,直接向stack1中加入元素即可。
出队操作步骤:
- 先判断stack2中有没有元素,有就直接弹出stack2[len(stack2) - 1]的元素。
- 如果stack2中没有元素,判断stack1中有没有元素,没有就直接返回-1。
- 如果stack1中有元素,就循环将stack1中的元素全部放进stack2中。
- 弹出stack2[len(stack2) - 1]的元素即可。
type CQueue struct {
stack1, stack2 []int
}
func Constructor() CQueue {
return CQueue{
stack1: make([]int, 0),
stack2: make([]int, 0),
}
}
func (this *CQueue) AppendTail(value int) {
this.stack1 = append(this.stack1, value)
}
func (this *CQueue) DeleteHead() int {
// 首先判断stack2是不是空的
if len(this.stack2) == 0 {
// 如果stack1是空的就返回-1
if len(this.stack1) == 0 {
return -1
}
for len(this.stack1) != 0 {
// 将stack1的元素移动到stack2
this.stack2 = append(this.stack2, this.stack1[len(this.stack1)-1])
// 删除stack1中的元素
this.stack1 = this.stack1[:len(this.stack1)-1]
}
}
val := this.stack2[len(this.stack2)-1]
this.stack2 = this.stack2[:len(this.stack2)-1]
return val
}
剑指 Offer 31. 栈的压入、弹出序列
题解
pushed是栈内元素,popped是栈内元素出栈的一个序列,所以我们可以模拟出栈的一个过程来判断popped序列是否是一个正确的序列。
步骤:
- 定义一个st栈,用于模拟入栈和出栈,再定义一个j循环变量用于遍历popped序列。
- 循环遍历pushed数组,将元素压入st栈中,同时循环判断
len(st) > 0 && st[len(st)-1] == popped[j]
,如果st的栈顶元素与出栈序列元素值相同就出栈,然后累加j变量。 - 最后直接返回
len(st) == 0
,如果序列正确,那么st数组为空,否则不为空。
func validateStackSequences(pushed []int, popped []int) bool {
// st数组用于模拟栈的出栈与入栈
st := make([]int, 0)
// 循环变量j用于遍历出栈序列popped
var j int
for i := 0; i < len(pushed); i++ {
// st入栈
st = append(st, pushed[i])
// 如果st的栈顶元素与出栈序列元素值相同就出栈
for len(st) > 0 && st[len(st)-1] == popped[j] {
// 出栈
st = st[:len(st)-1]
j++
}
}
return len(st) == 0
}