Leetcode-栈相关


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中加入元素即可。

出队操作步骤:

  1. 先判断stack2中有没有元素,有就直接弹出stack2[len(stack2) - 1]的元素。
  2. 如果stack2中没有元素,判断stack1中有没有元素,没有就直接返回-1。
  3. 如果stack1中有元素,就循环将stack1中的元素全部放进stack2中。
  4. 弹出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序列是否是一个正确的序列。

步骤:

  1. 定义一个st栈,用于模拟入栈和出栈,再定义一个j循环变量用于遍历popped序列。
  2. 循环遍历pushed数组,将元素压入st栈中,同时循环判断len(st) > 0 && st[len(st)-1] == popped[j],如果st的栈顶元素与出栈序列元素值相同就出栈,然后累加j变量。
  3. 最后直接返回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
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值