题目来源:books.halfrost.com/leetcode/Ch…
1700.无法吃午餐的学生
// 这道题,还是挺有意思的,有两个考虑的角度,一个是从人的角度考虑,一个是从三明治的角度考虑
// 这里,我们从三明治的角度考虑,将栈顶的三明治分配给,一名学生,当这个三明治没人要的时候,结束循环 ;
func countStudents(students []int, sandwiches []int) int {
mp := make(map[int]int)
for _, s := range students {
mp[s]++
}
// 记录分了多少三明治
cnt := 0
for _, s := range sandwiches {
if mp[s] <= 0 {
return len(sandwiches) - cnt
}
mp[s]--
cnt++
}
return 0
}
1673.找出最具竞争力的子序列
什么时候才适合用单调递归栈呢?当我们需要维持元素之间相对顺序的时候,可以考虑使用单调栈;单调栈需要考虑:
- 为了保证栈的特性,什么时候出栈?
// 这道题能想到的一种解法,是找出所有的长度为k的子序列,然后在一个个比较,哪个更具有竞争力
// 单调栈的解法,什么时候,我们才会释放栈中元素?
// 当,[i, len(nums)) + 当前栈中元素,大于 k个元素,并且,当前元素小于栈顶元素的时候,需要释放栈尾元素
// 当我们希望,保持的是一个单调递增栈(栈中元素要保证递增且当前元素一定是要入栈的)
// 那么,我们就可以通过比较栈顶元素与当前处理元素的关系来,确定是否需要出栈
func mostCompetitive(nums []int, k int) []int {
sk := []int{}
for i := 0; i < len(nums); i++ {
// 为了保证栈中元素递增,需要校验出栈条件
for len(sk) + len(nums) - i > k && len(sk) > 0 && nums[i] < sk[len(sk)-1] {
sk = sk[:len(sk)-1]
}
sk = append(sk, nums[i])
}
// [0, k), 左闭右开区间,这里只有k个元素
return sk[:k]
}
1653.使字符串平衡的最少删除次数
// 1. 动态规划求解
// 如果当前元素为b,保留b,就是前面元素的解;
// 如果当前元素为a,保留a,删除前面的b,或者前面pre + 1;
// 2. 模拟分解法求解
// dp[i]表示区间[0, i)之间需要删除多少次
// 当有时需要考虑越界问题的时候,可以考虑左闭右开区间;
// 这道题,重点知道状态转移方程如何写
func minimumDeletions(s string) int {
dp := make([]int, len(s) + 1)
bCount := 0
// 以字符串为标杆,来计算dp数组的内容
for i := 0; i< len(s); i++ {
if s[i] == 'a' {
dp[i+1] = min(dp[i] + 1, bCount)
} else if s[i] == 'b' {
dp[i+1] = dp[i]
bCount++
}
}
return dp[len(s)]
}
1614.括号的最大嵌套深度
// 这道题,定义cnt,左括号++,右括号--,然后统计最大结果值
// 这种小题也挺有意思的,这里的++,--,是不是可以考虑成入栈与出栈的意思呢
func maxDepth(s string) int {
cnt, res := 0, 0
for i := 0;i < len(s); i++ {
if s[i] == '(' {
cnt++
res = max(res, cnt)
} else if s[i] == ')'{
cnt--
}
}
return res
}
1249.移除无效的括号
为了达到平衡,标准是余量>=0, 并且到整个字符串末尾余量一定为0,当余量大于0时,需要从右到左,删除最先遇到的左括号;
// 括号合法,余量始终需要大于等于0,且最后余量必须为0;
// 余量 = 左括号数 - 右括号数
// 那么,可以使用cnt记录余量,当计算过程中为0表示,需要删除右括号;
// 当遍历完整个字符串后,cnt>0,那么需要从头在遍历一会,删除左括号
// 重点在余量的计算上
func minRemoveToMakeValid(s string) string {
cnt := 0
res := []byte{}
for i := 0; i < len(s); i++ {
if s[i] == '(' {
cnt++
} else if s[i] == ')' {
if cnt > 0 {
cnt--
} else {
continue
}
}
res = append(res, s[i])
}
// 多于的左括号一定是在右边,结论确实是这样的,那又改怎么理解呢?这里我还是还是想不明白啊
for i := len(res)-1; i >= 0; i-- {
if res[i] == '(' && cnt > 0 {
// 这里有个习惯,应该是执行完操作以后,在减去标记数(因为业务开发过程中,标记数修改出现的问题,远小于业务代码)
res = append(res[:i], res[i+1:]...)
cnt--
}
}
return string(res)
}
1209.删除字符串中的所有相邻重复项 II
暴力法:
// 这道题的暴力解法,也有必要学习一下
// 题意:删除原有字符串中部分字符,重新生成新的字符,对于这种,如果没有极限的空间复杂度的要求
// 可以将数组移动到另外一个地方,在进行相应的逻辑处理;
// 为什么需要摞个地,因为这样更加方便操作
// 我们不应该看了一遍答案以后,靠记忆力来重写这类题,而是应该考逻辑来解题;
// 当遍历处理每个节点的时候,如果该节前与前面节点凑成k个,就把这些节点删除,直到遍历完真个字符串
func removeDuplicates(s string, k int) string {
arr := []byte{}
for i := 0; i < len(s); i++ {
arr = append(arr, s[i])
for len(arr) > 0 {
tmp := arr[len(arr)-1]
cnt := 0
for i := len(arr) - 1; i >= 0; i-- {
if arr[i] == tmp {
cnt++
} else {
break
}
}
if cnt == k {
arr = arr[:len(arr) - k]
} else {
break
}
}
}
return string(arr)
}
暴力解法,在于每次遇到新的字符串的时候,都需要重新检查字符串前面的值,如果我们使用栈,记录该字符串出现的次数,那么就可以消除重复计算了;
栈解法:
func removeDuplicates(s string, k int) string {
type Node struct {
cnt int // 字符出现的次数
char byte // 每个字符
}
sk := []Node{}
for i := 0; i < len(s); i++ {
// 如果等于栈顶元素
if len(sk) > 0 && sk[len(sk)-1].char == s[i] {
sk[len(sk)-1].cnt++
if sk[len(sk)-1].cnt == k {
sk = sk[:len(sk)-1]
}
} else {
// 如果不等于栈顶元素
sk = append(sk, Node{1, s[i]})
}
}
// 将栈里面的内容,合成一个新的字符串
by := []byte{}
for i := 0; i < len(sk); i++ {
for j := 0; j < sk[i].cnt; j++ {
by = append(by, sk[i].char)
}
}
return string(by)
}
1190.反转每对括号间的子串
// 字符串的反转,那么,我们不能在原字符串串上进行修改,可以将其转为[]byte,然后进行调整;
func reverseParentheses(s string) string {
arr := []byte(s)
// 存左右括号的下标
sk := []int{}
for i := 0; i < len(s); i++ {
if s[i] == '(' {
sk = append(sk, i)
} else if s[i] == ')' {
star, end := sk[len(sk)-1]+1, i-1
for star < end {
arr[star], arr[end] = arr[end], arr[star]
star++
end--
}
sk = sk[:len(sk)-1]
}
}
// 这里为啥不使用
sb := strings.Builder{}
for i := 0; i < len(arr); i++ {
if arr[i] != '(' && arr[i] != ')' {
sb.WriteByte(arr[i])
}
}
// 这种代码一定不要写,会有问题;
// for i := 0; i < len(arr); i++ {
// if arr[i] == '(' || arr[i] == ')' {
// arr = append(arr[:i], arr[i+1:]...)
// }
// }
return sb.String()
}
1111.有效括号的嵌套深度
// 这个新概念真的不好理解,首先,明白什么是嵌套深度;
// 如果当前处理的是(,将其入栈以后,栈的高度,就是当前字符的嵌套深度;
// 如果当前处理的是),当前栈的高度,就是当前的字符的嵌套深度,然后将(出栈;
// 我们将嵌套深度为奇数的分配个给一个序列,嵌套深度为偶数的分配个另外一个序列;
func maxDepthAfterSplit(seq string) []int {
cnt := 0
ans := make([]int, 0)
for i := 0; i < len(seq); i++ {
if seq[i] == '(' {
cnt++
ans = append(ans, cnt % 2)
} else {
ans = append(ans, cnt % 2)
cnt--
}
}
return ans
}
1047.删除字符串中的所有相邻重复项
func removeDuplicates(s string) string {
// 需要将当前字符与前面的字符进行对比,那么可以考虑使用栈来解决这个问题;
// 这道题,用来提高信息是非常好的
sk := []byte{}
for i := 0; i < len(s); i++ {
// 判断是否需要出栈
if len(sk) != 0 && sk[len(sk)-1] == s[i] {
sk = sk[:len(sk)-1]
} else {
sk = append(sk, s[i])
}
}
return string(sk)
}
1021.删除最外层的括号
// 有效括号,题给定的就是有效括号
// 将题意转为栈的形式,入栈,栈高++,出栈,栈高--;
// 当栈为0时,去掉最外层的括号,就是所求的值;
// 入栈时候,栈为0的字符去掉,出栈是,为0 的字符去掉;
// 我们的目的还是修改原字符,按照规定保留部分字符
func removeOuterParentheses(s string) string {
cnt := 0
ans := []byte{}
// 遍历原字符串,按照规定取部分字符出来
for i := 0; i < len(s); i++ {
// 这两者之间的顺序,需要注意,也就是,检查 - 执行的顺序;
if s[i] == ')' {
cnt--
}
if cnt >0 {
ans = append(ans, s[i])
}
if s[i] == '(' {
cnt++
}
}
return string(ans)
}
1019.链表中的下一个更大节点
/**
* Definition for singly-linked list.
* type ListNode struct {
* Val int
* Next *ListNode
* }
*/
// 下一个更大的节点,往右边看,第一个比当前节点大的节点
// 这里可以考虑使用单调栈的方式来求解:单调递减栈
// 如果当前元素,比栈顶元素小,入栈;
// 如果当前元素,比栈顶元素大,出栈,直到栈为空,或者当前元素比栈顶元素小;
// 因为需要修改元素的值,所以栈中元素需要保留结果值所在的下标
func nextLargerNodes(head *ListNode) []int {
type Node struct {
Index int
Val int
}
sk := []Node{}
res := []int{}
cur := head
for cur != nil {
// 出栈逻辑
for len(sk) != 0 && sk[len(sk)-1].Val < cur.Val {
// 出栈节点的坐标,为res中的坐标
res[sk[len(sk)-1].Index] = cur.Val
sk = sk[:len(sk)-1]
}
res = append(res, 0)
sk = append(sk, Node{len(res)-1, cur.Val})
cur = cur.Next
}
return res
}
1003.检查替换后的词是否有效
// 重构问题,本题给定字符串s,如果s有效,需要保证是abc的倍数,并且abc是有序的
// 所以可以考虑使用栈来解决这个问题,当我们使用栈的时候,需要考虑什么时候入栈,什么时候出栈
// 入栈:任何字符均可入栈;
// 出栈:当字符串与栈内组成“abc”的时候,考虑出栈;
// 这里需要注意两个常识:sk[len(sk)-3:], 有3个元素;
// sk[:len(sk)-3],去掉栈尾的三个元素
func isValid(s string) bool {
sk := []byte{}
for i := 0; i < len(s); i++ {
sk = append(sk, s[i])
if len(sk) >= 3 && string(sk[len(sk)-3:]) == "abc" {
sk = sk[:len(sk)-3]
}
}
return len(sk) == 0
}
946.验证栈序列
解法一:看到这道题,第一想法是这样的,但是过于复杂;
// 重构问题
// 这道题,肯定是用栈来解决,用栈的话,需要考虑两个问题
// 什么时候入栈?当pushed处理的元素,不等于poped处理元素时入栈;
// 什么时候出栈?当两者相等时,出栈,直到不相等为止;
func validateStackSequences(pushed []int, popped []int) bool {
i, j := 0, 0
sk := []int{}
for i < len(pushed) && j < len(popped) {
if pushed[i] != popped[j] {
sk = append(sk, pushed[i])
i++
} else {
i++
j++
for j < len(popped) && len(sk) >0 && sk[len(sk)-1] == popped[j] {
sk = sk[:len(sk)-1]
j++
}
}
}
return len(sk) == 0
}
解法二:霜神的解法
// 刚刚那解法,过于繁琐了,因为,我们控制的是,待处理的字符什么时候入栈,换个思路
// 将所有的字符,全部入栈,入栈以后,在检测是否满足出栈的条件
// 最后,只要待处理的字符等于pushed的长度,表示符合要求
func validateStackSequences(pushed []int, popped []int) bool {
sk, j, N := []int{}, 0, len(pushed)
for i := 0; i < N; i++ {
sk = append(sk, pushed[i])
for j < N && len(sk) > 0 && sk[len(sk)-1] == popped[j] {
sk = sk[:len(sk)-1]
j++
}
}
return j == N
}
921.使括号有效的最少添加
// 有效括号的题,写过挺多回了
// 在脑子里面,寻找以前有过的经验,发现可以用栈来求解,最后栈的长度,就表示需要添加的最少括号数
func minAddToMakeValid(s string) int {
sk := []byte{}
for i := 0; i < len(s); i++ {
if s[i] == '(' {
sk = append(sk, s[i])
} else if s[i] == ')' && len(sk) > 0 && sk[len(sk)-1] == '(' {
sk = sk[:len(sk)-1]
} else {
sk = append(sk, s[i])
}
}
return len(sk)
}
901.股票价格跨度
type Node struct {
Val int
Res int
}
type StockSpanner struct {
Item []Node
}
// 【重构问题】这道题的第一映像,就应该使用栈,那么栈里面存什么呢?存下标,还是具体的数值呢?
// 这里,我们需要计算两个差值,所以,应该存的是下标
// 这,应该是一个单调递减栈,所以,当元素小于等于栈等元素时,入栈,此时为1;
// 出栈,当元素大于栈顶元素时,出栈,直到符合条件
func Constructor() StockSpanner {
stockSpanner := StockSpanner{make([]Node, 0)}
return stockSpanner
}
// 我觉得,霜神的这种写法,更加合适,如果这样写,我们就不需要关注小标的各种问题
// 当前处理函数,就是栈顶元素+1
func (this *StockSpanner) Next(price int) int {
// 提前定义一个res变量,这样更加好处理,也可简化后续的代码逻辑
res := 1
if len(this.Item) == 0 {
this.Item = append(this.Item, Node{price, res})
return res
}
// 单调栈的惯用逻辑,单调递减栈
for len(this.Item) > 0 && this.Item[len(this.Item)-1].Val <= price {
res = res + this.Item[len(this.Item)-1].Res
this.Item = this.Item[:len(this.Item)-1]
}
this.Item = append(this.Item, Node{price, res})
return res
}
/**
* Your StockSpanner object will be instantiated and called as such:
* obj := Constructor();
* param_1 := obj.Next(price);
*/
【注】:单调栈,请都用这种写法,更加有利于建立个人体系;
739.每日温度
// 这里明显使用单调栈来来求解,而且是单递减栈
// 首先,定义一个输出的数组,当元素出栈的时候,更新数组中的值
// 更新的规则是,将当前数的下标,减去出栈数的下标
func dailyTemperatures(temperatures []int) []int {
res := make([]int, len(temperatures))
sk := []int{}
for i, v := range temperatures {
for len(sk) > 0 && temperatures[sk[len(sk)-1]] < v {
idx := sk[len(sk)-1]
res[idx] = i - idx
sk = sk[:len(sk)-1]
}
sk = append(sk, i)
}
return res
}
682.棒球比赛
// 根据当前处理的数,对数组前面的数进行处理,所以这里考虑使用栈来求解
func calPoints(operations []string) int {
sk := []int{}
for i := 0; i < len(operations); i++ {
op := operations[i]
switch op {
case "+":
last1 := sk[len(sk)-1]
last2 := sk[len(sk)-2]
sk = append(sk, last1 + last2)
case "D":
last1 := sk[len(sk)-1]
sk = append(sk, last1 * 2)
case "C":
sk = sk[:len(sk)-1]
default:
c, _ := strconv.Atoi(op)
sk = append(sk, c)
}
}
var res int
for i := 0; i < len(sk); i++ {
res += sk[i]
}
return res
}
【注】:这些常用的字符串处理API还是要好好考虑一下的啊;
907.子数组的最小值之和
// 重构问题:给定一个数组,找出其中所有的子数组,然后获取每个子数组的最小值,最后计算所有最小值的和
func sumSubarrayMins(arr []int) int {
res, mod := 0, 1000000007
for i := 0; i < len(arr); i++{
tmp := arr[i]
for j := i; j < len(arr); j++ {
if arr[j] < tmp {
tmp = arr[j]
}
res += tmp
}
}
return res % mod
}
【注】该写法会超时,但是也有必要好好理解下
单调栈+动态规划
// 递归+单调栈的方法,这道题,还是很有意思的呢
// 之前,我们说过,这道题的暴力解法是,找出所有的子数组,然后计算每个子数组的最小值,最后将所有最小值相加
// 换个思路,所有子数组,是不是 等价于 所有以A[i]结尾的子数组
// 当我们找全了所有以A[i]结尾的子数组之后,结果集就完备了
// 所以,我们确定用栈来保存以A[i]结尾的所有子数组,当我们遍历每个元素的时候,就相当于栈的入栈和出栈了
type Node struct {
num int // 元素的值
cnt int // 元素的个数
}
func sumSubarrayMins(arr []int) int {
sk := []Node{}
// res, 最后的结果集
// tmp,为以A[i]结尾,所有子数组最小值的和
res, tmp := 0, 0
mod := 1000000007
// 单调递增栈
for i := 0; i < len(arr); i++ {
cnt := 1
// 出栈操作
for len(sk) > 0 && sk[len(sk)-1].num > arr[i] {
node := sk[len(sk)-1]
tmp -= node.num * node.cnt
cnt += node.cnt
sk = sk[:len(sk)-1]
}
// 以nums[i]结尾的所有子数组中,nums[i]为最小的次数为cnt
sk = append(sk, Node{arr[i], cnt})
tmp += arr[i] * cnt
res += tmp
}
return res % mod
}