六 栈堆
Images Source: https://realpython.com/
目录
- 六 栈堆
- 1 堆栈
- 1.1 基础知识
- 1.2 相关题目
- 1.2.1 [155 . 最小栈](https://leetcode-cn.com/problems/min-stack/)
- 1.2.2 [20 . 有效的括号](https://leetcode-cn.com/problems/valid-parentheses/)
- 1.2.3 [227 . 基本计算器 II](https://leetcode-cn.com/problems/basic-calculator-ii/)
- 1.2.4 [150 . 逆波兰表达式求值](https://leetcode-cn.com/problems/evaluate-reverse-polish-notation/)
- 1.2.5 [739 . 每日温度](https://leetcode-cn.com/problems/daily-temperatures/)
- 1.2.6 [232 . 用栈实现队列](https://leetcode-cn.com/problems/implement-queue-using-stacks/)
- 1.2.7 [394 . 字符串解码](https://leetcode-cn.com/problems/decode-string/)
- 2 深度优先搜索
- 2.1 基础知识
- 2.2 相关题目
- 2.2.1 [200 . 岛屿数量](https://leetcode-cn.com/problems/number-of-islands/)
- 2.2.2 [133 . 克隆图](https://leetcode-cn.com/problems/clone-graph/)
- 2.2.3 [841 . 钥匙和房间](https://leetcode-cn.com/problems/keys-and-rooms/)
- 2.2.4 [695 . 岛屿的最大面积](https://leetcode-cn.com/problems/max-area-of-island/)
- 2.2.5 [130 . 被围绕的区域](https://leetcode-cn.com/problems/surrounded-regions/)
- 2.2.6 [494 . 目标和](https://leetcode-cn.com/problems/target-sum/)
- 2.2.7 [417 . 太平洋大西洋水流问题](https://leetcode-cn.com/problems/pacific-atlantic-water-flow/)
- 2.2.8 [1020 . 飞地的数量](https://leetcode-cn.com/problems/number-of-enclaves/)
- 2.2.9 [1020 . 飞地的数量](https://leetcode-cn.com/problems/number-of-enclaves/)
- 3 单调栈
来源
Datewhale33期__LeetCode 刷题 :
-
航路开辟者:杨世超
-
领航员:桐
-
航海士:杨世超、李彦鹏、叶志雄、赵子一
-
开源电子书
https://algo.itcharge.cn
1 堆栈
1.1 基础知识
只允许在一端进行插入或删除操作的线性表。
首先,栈是一种线性表,但限定这种线性表只能在某一段进行插入和删除操作。
→→→→ 栈是一种只能从表的一端存取数据且遵循 “先进后出” 原则的线性存储结构
- 存储结构
包含
栈顶(Top):线性表允许进行插入和删除的一端。
栈底(Bottom):固定的,不允许进行插入和删除的另一端。
空栈:不含任何元素。
-
两种实现方式
- 顺序栈:采用顺序存储结构可以模拟栈存储数据的特点,从而实现栈存储结构;
- 链栈:采用链式存储结构实现栈结构
-
区别: 物理存储位置区别--------------顺序栈底层采用的是数组,链栈底层采用的是链表
-
顺序栈实现及基本操作
class Stack:
#初始化
def __init__(self, size = 100):
self.top = -1
self.size = size
self.stack = []
#判断是否为空
def is_None(self):
return self.top == -1
#判断是否满栈
def is_full(self):
return self.top + 1 == self.size
#入栈
def push(self, val):
if self.is_full():
raise Exception("Stack is full!")
else:
self.stack.append(val)
self.top += 1
#出栈
def pop(self):
if self.is_None():
raise Exception("Stack is empty")
else:
self.stack.pop()
self.top -= 1
#获得栈顶
def peek(self):
if self.is_None():
raise Exception("Stack is empty")
else:
return self.stack[self.top]
- 链栈实现及基本操作
class ListNode:
def __init__(self, val):
self.val = val
self.next = next
class Stack:
#初始化
def __init__(self):
self.top = None
def is_None(self):
return self.top == None
def push(self, val):
cur = ListNode(val)
cur.next = self.top
self.top = cur
def pop(self):
if self.is_None():
raise Exception("Stack is empty")
else:
cur = self.top
self.top = self.top.next
del cur
def peek(self):
if self.is_None():
raise Exception("Stack is empty")
else:
return self.top.val
-
时间复杂度
- Push (入栈):O(1)
- Pop (出栈):O(1)
- Access (访问栈顶):O(1)
- python中
栈、队列、双端队列可以用 list 实现
优先队列可以用 heapq 库
1.2 相关题目
1.2.1 155 . 最小栈
- 思路: 利用另一个栈顶存最小值
class MinStack:
def __init__(self):
self.stack = []
self.minstack = []
def push(self, val: int) -> None:
self.stack.append(val)
if not self.minstack or self.minstack[-1] >= val: #入栈同时更新min
self.minstack.append(val)
def pop(self) -> None:
if self.minstack[-1] == self.stack[-1]:
self.minstack.pop()
self.stack.pop()
def top(self) -> int:
return self.stack[-1]
def getMin(self) -> int:
return self.minstack[-1]
1.2.2 20 . 有效的括号
- 思路: 利用栈存左边括号
("(", "{", "[")
, 首次出现右括号时考虑能否与stack[- 1]
配对消去 >>> 直至stack
为空
class Solution:
def isValid(self, s: str) -> bool:
if len(s) % 2 != 0:return False
stack = [] #储存未配对的左括号
d = { ')' : '(', #哈希储存对应关系
']' : '[',
'}' : '{'}
for i in s:
if stack and i in d: #若在栈非空出现右括号
if d[i] == stack[-1]:
stack.pop()
else:
return False
# 若栈为空 或 左括号入栈
else:
stack.append(i)
return not stack
1.2.3 227 . 基本计算器 II
- 思路:
加法 >>> num入栈
减法 >>> -num入栈
乘法 >>> 栈顶*num 入栈
除法 >>> 栈顶 / num 入栈
最后计算栈和
class Solution:
def calculate(self, s: str) -> int:
stack = []
op = '+'
num = 0
for i in range(len(s)):
if s[i].isdigit():
num = num * 10 + int(s[i])
if i == len(s) - 1 or s[i] in "+-*/":
if op == '+':
stack.append(num)
elif op == '-':
stack.append(-num)
elif op == '*':
stack.append(stack.pop()*num)
elif op == '/':
stack.append(int(stack.pop()/num))
op = s[i]
num = 0
return sum(stack)
1.2.4 150 . 逆波兰表达式求值
- 思路: 与上一类似思路 >>> 注意
isdigit()
只能判别整正数, 用token[-1]
判别即可
class Solution:
def evalRPN(self, tokens: List[str]) -> int:
stack = []
for token in tokens:
if token[-1].isdigit():
stack.append(int(token))
if token in "+-*/":
op = token
p = stack.pop()
if op == '+':
stack[-1] += p
elif op == '-':
stack[-1] -= p
elif op == '*':
stack[-1] = stack[-1] * p
elif op == '/':
stack[-1] = int(stack[-1] / p)
return stack[-1]
1.2.5 739 . 每日温度
- 思路: 维护递减单调栈即可
class Solution:
def dailyTemperatures(self, temperatures: List[int]) -> List[int]:
stack,ans = [],[0]*len(temperatures)
for i in range(len(temperatures)):
while stack and temperatures[stack[-1]] < temperatures[i]:
index = stack.pop()
ans[index] = i - index
stack.append(i)
return ans
1.2.6 232 . 用栈实现队列
class MyQueue:
def __init__(self):
self.stack = []
self.stack_1 = []
def push(self, x: int) -> None:
self.stack.append(x)
def pop(self) -> int:
if not self.stack_1:
while self.stack:
self.stack_1.append(self.stack.pop())
return self.stack_1.pop()
def peek(self) -> int:
if not self.stack_1:
while self.stack:
self.stack_1.append(self.stack.pop())
return self.stack_1[-1]
def empty(self) -> bool:
return not self.stack_1 and not self.stack
1.2.7 394 . 字符串解码
class Solution:
def decodeString(self, s: str) -> str:
string = []
nums = []
num = 0
res = ''
for ch in s:
if ch.isdigit():
num = num * 10 + int(ch)
elif ch == '[':
string.append(res)
nums.append(num)
res = ''
num = 0
elif ch == ']':
cur_res = string.pop()
cur_num = nums.pop()
res = cur_res + res * cur_num
else:
res += ch
return res
2 深度优先搜索
2.1 基础知识
深度优先搜索算法(Depth First Search):英文缩写为 DFS。是一种用于遍历或搜索树或图的算法。该算法沿着树的深度遍历树的节点,会尽可能深的搜索树的分支。当节点 v 的所在边都己被探寻过,搜索将回溯到发现节点 v 的那条边的起始节点。这一过程一直进行到已发现从源节点可达的所有节点为止。如果还存在未被发现的节点,则选择其中一个作为源节点并重复以上过程,整个进程反复进行直到所有节点都被访问为止。
- 深度优先遍历
(1 从图中的某个初始点 v 出发,首先访问初始点 v.
(2 选择一个与顶点 v 相邻且没被访问过的顶点 ver ,以 ver 为初始顶点,再从它出发进行深度优先遍历。
(3 当路径上被遍历完,就访问上一个顶点的第 二个相邻顶点。
(4 直到所有与初始顶点 v 联通的顶点都被访问。
- DFS与BFS对比:
- DFS更适合搜索树形状态空间
•递归本身就会产生树的结构
•可以用一个全局变量维护状态中较为复杂的信息(例:子集方案、排列方案)
•不需要队列,节省空间 - 求"最小代价"、"最少步数"的题目,用BFS
• BFS是按层次序搜索,第k步搜完才会搜k+1步,在任意时刻队列中至多只有两层 - 状态空间为有向无环图
• BFS拓扑排序/ DFS记忆化搜索均可
- DFS更适合搜索树形状态空间
2.2 相关题目
2.2.1 200 . 岛屿数量
- DFS
class Solution:
def numIslands(self, grid: List[List[str]]) -> int:
m, n, ans = len(grid),len(grid[0]), 0
visited = [[False] * n for _ in range(m)]
dx = [-1, 0, 0, 1]
dy = [0, 1, -1, 0]
def dfs(grid, x, y):
visited[x][y] = True
for k in range(4):
nx = x + dx[k]
ny = y + dy[k]
if nx < 0 or ny < 0 or nx >= m or ny >= n or visited[nx][ny] or grid[nx][ny] == '0':
continue
dfs(grid, nx, ny)
for i in range(m):
for j in range(n):
if grid[i][j] == '1' and not visited[i][j]:
dfs(grid, i, j)
ans += 1
return ans
- DFS二 >>> 答案中将已经遍历过的
'1'
点改为'0'
, 节省空间代码也更简洁 >>> 妙
class Solution:
def numIslands(self, grid: List[List[str]]) -> int:
m, n, ans = len(grid),len(grid[0]), 0
def dfs(grid, x, y):
if x < 0 or y < 0 or x >= m or y >= n or grid[x][y] == '0':
return
grid[x][y] = '0'
dfs(grid, x + 1, y)
dfs(grid, x, y + 1)
dfs(grid, x - 1, y)
dfs(grid, x, y - 1)
for i in range(m):
for j in range(n):
if grid[i][j] == '1':
dfs(grid, i, j)
ans += 1
return ans
- BFS
class Solution:
def numIslands(self, grid: List[List[str]]) -> int:
m, n, ans = len(grid),len(grid[0]), 0
def bfs(x, y):
from collections import deque
q = deque()
dx = [-1, 0 , 0, 1] # 行的变化: 上 右 左 下
dy = [0, 1, -1, 0] # 列的变化: 上 右 左 下
q.append([x, y])
grid[x][y] = '0'
while q:
sx, sy = q[0][0], q[0][1]
q.popleft()
for k in range(4):
nx = sx + dx[k]
ny = sy + dy[k]
if nx < 0 or ny < 0 or nx >= m or ny >= n or grid[nx][ny] == '0':
continue
q.append([nx, ny])
grid[nx][ny] = '0'
for i in range(m):
for j in range(n):
if grid[i][j] == '1':
bfs(i, j)
ans += 1
return ans
2.2.2 133 . 克隆图
- 思路: 建立
visvit
哈希使得旧点 : 新点
, 遍历老点关系用dfs建立新节点对应关系
class Solution:
def cloneGraph(self, node: 'Node') -> 'Node':
if not node: return node
visvited = {}
return self.dfs(node,visvited)
def dfs(self, node, visvited):
if node in visvited:
return visvited[node]
new_grap = Node(node.val, [])
visvited[node] = new_grap #建立新老对应关系
for i in node.neighbors:
new_grap.neighbors.append(self.dfs(i, visvited))
return new_grap
2.2.3 841 . 钥匙和房间
- 思路: 简单的dfs
class Solution:
def canVisitAllRooms(self, rooms: List[List[int]]) -> bool:
visvited = [False] * (len(rooms))
self.dfs(rooms, 0,visvited)
for i in range(len(rooms)):
if not visvited[i]:
return False
return True
def dfs(self, rooms, i, visvited):
visvited[i] = True
for k in rooms[i]:
if not visvited[k]:
self.dfs(rooms, k, visvited)
- 思路二 dfs优化 >>>> 利用set()的递归dfs
class Solution:
def canVisitAllRooms(self, rooms: List[List[int]]) -> bool:
def dfs(i):
visvited.add(i)
for k in rooms[i]:
if k not in visvited:
dfs(k)
visvited = set()
dfs(0)
return len(visvited) == len(rooms)
2.2.4 695 . 岛屿的最大面积
- 思路: 与200题类似dfs
class Solution:
def maxAreaOfIsland(self, grid: List[List[int]]) -> int:
m, n, ans = len(grid), len(grid[0]), 0
def dfs(x, y):
grid[x][y] = 0
s = 1
dx = [-1, 0, 0, 1]
dy = [0, 1, -1, 0]
for k in range(4):
nx = x + dx[k]
ny = y + dy[k]
if nx < 0 or ny < 0 or nx >= m or ny >= n or grid[nx][ny] == 0:
continue
s += dfs(nx, ny)
return s
for i in range(m):
for j in range(n):
if grid[i][j] == 1:
ans = max(ans, dfs(i, j))
return ans
2.2.5 130 . 被围绕的区域
- 思路: 先利用dfs搜索将四周的
'O'
改为'Q'
>>>> 最后遍历整个图 将'Q'
变回'O'
,其他'O'
全变为'X'
即可
1.dfs
class Solution:
def solve(self, board: List[List[str]]) -> None:
m, n = len(board), len(board[0])
def dfs(x, y):
if board[x][y] != 'O':return
board[x][y] = 'Q'
dx = [-1, 0, 0, 1]
dy = [0, 1, -1, 0]
for k in range(4):
nx = x + dx[k]
ny = y + dy[k]
if 0 < nx < m and 0 < ny < n:
dfs(nx, ny)
for row in range(m): #第一列 及最后一列搜索"O"及相连"O"
dfs(row, 0)
dfs(row, n - 1)
for col in range(n): # 第一行及最后一行
dfs(0, col)
dfs(m - 1, col)
for i in range(m):
for j in range(n):
board[i][j] = 'O' if board[i][j] == 'Q' else 'X'
2.bfs
class Solution:
def solve(self, board: List[List[str]]) -> None:
m, n = len(board), len(board[0])
def bfs(x, y):
from collections import deque
q = deque()
board[x][y] = 'Q'
q.append([x, y])
dx = [0, -1, 1, 0]
dy = [1, 0, 0, -1]
while q:
x, y = q.popleft()
for k in range(4):
nx = x + dx[k]
ny = y + dy[k]
if 0 <= nx < m and 0 <= ny < n and board[nx][ny] == 'O':
board[nx][ny] = 'Q'
q.append([nx, ny])
for row in range(m):
if board[row][0] == 'O':
bfs(row, 0)
if board[row][n - 1] == 'O':
bfs(row, n - 1)
for col in range(n):
if board[0][col] == 'O':
bfs(0, col)
if board[m - 1][col] == 'O':
bfs(m - 1, col)
for i in range(m):
for j in range(n):
board[i][j] = 'O' if board[i][j] == 'Q' else 'X'
2.2.6 494 . 目标和
- 思路:
target = 正数和 - 负数和 = x - y
数组总和sum:sum = x + y
可以求出 x = (S + sum) / 2 = size
动规 >>> 转移方程为:dp[i] = dp[i] + dp[i - num]
当前填满容量为i的包的方法数 = 之前填满容量为i的包的方法数 + 之前填满容量为i - num的包的方法数
dp[i]
>>> 和为i
,有dp[i]
种方法
class Solution:
def findTargetSumWays(self, nums: List[int], target: int) -> int:
s = sum(nums)
if abs(target) > abs(s) or (target + s) % 2 == 1:return 0
size = (target + s) // 2
dp = [0 for _ in range(size + 1)]
dp[0] = 1 #填满容量为0的背包有且只有一种方法
for num in nums:
for i in range(size, num - 1, -1):
dp[i] = dp[i] + dp[i - num]
return dp[size]
- DFS >>> +或-
nums[i]
>>> 直到等于target
class Solution:
def findTargetSumWays(self, nums: List[int], target: int) -> int:
d = {}
def dfs(cur, i, d):
if i < len(nums) and (cur, i) not in d: # 搜索周围节点
d[(cur, i)] = dfs(cur + nums[i], i + 1, d) + dfs(cur - nums[i],i + 1, d)
return d.get((cur, i), int(cur == target))
return dfs(0, 0, d)
2.2.7 417 . 太平洋大西洋水流问题
- 思路: 从四周的太平洋和大西洋出发, 搜索递增高度
class Solution:
def pacificAtlantic(self, heights: List[List[int]]) -> List[List[int]]:
m, n, ans = len(heights), len(heights[0]), []
visvited_1 = [[False] * n for _ in range(m)]
visvited_2 = [[False] * n for _ in range(m)]
def dfs(x, y, visvited):
visvited[x][y] = True
dx = [0, -1, 1, 0]
dy = [1, 0, 0, -1]
for k in range(4):
nx = x + dx[k]
ny = y + dy[k]
if nx < 0 or ny < 0 or nx >= m or ny >= n or visvited[nx][ny]:
continue
if heights[nx][ny] >= heights[x][y]:
dfs(nx, ny, visvited)
for row in range(m):
dfs(row, 0, visvited_1)
dfs(row, n - 1, visvited_2)
for col in range(n):
dfs(0, col, visvited_1)
dfs(m - 1, col, visvited_2)
for i in range(m):
for j in range(n):
if visvited_1[i][j] and visvited_2[i][j]:
ans.append([i, j])
return ans
2.2.8 1020 . 飞地的数量
- 思路: 与上题类似, 从矩阵四周
'1'
出发, 遍历搜索所有相连'1'
赋值为0 >>> 剩下即为不能出去的陆地
class Solution:
def numEnclaves(self, grid: List[List[int]]) -> int:
m, n, ans = len(grid), len(grid[0]), 0
def dfs(x, y):
grid[x][y] = 0
dx = [0, -1, 1, 0]
dy = [1, 0, 0, -1]
for k in range(4):
nx = x + dx[k]
ny = y + dy[k]
if nx < 0 or ny < 0 or nx >= m or ny >= n or grid[nx][ny] == 0:
continue
dfs(nx, ny)
for row in range(m):
if grid[row][0] == 1:
dfs(row, 0)
if grid[row][n - 1] == 1:
dfs(row, n - 1)
for col in range(n):
if grid[0][col] == 1:
dfs(0, col)
if grid[m - 1][col] == 1:
dfs(m - 1, col)
for i in range(m):
for j in range(n):
if grid[i][j] == 1:
ans += 1
return ans
2.2.9 1020 . 飞地的数量
class Solution:
def closedIsland(self, grid: List[List[int]]) -> int:
m, n, ans = len(grid), len(grid[0]), 0
def dfs(x, y):
grid[x][y] = 1
dx = [0, -1, 1, 0]
dy = [1, 0, 0, -1]
for k in range(4):
nx = x + dx[k]
ny = y + dy[k]
if nx < 0 or ny < 0 or nx >= m or ny >= n or grid[nx][ny] == 1:
continue
dfs(nx, ny)
for row in range(m):
if grid[row][0] == 0:
dfs(row, 0)
if grid[row][n - 1] == 0 :
dfs(row, n - 1)
for col in range(n):
if grid[0][col] == 0:
dfs(0, col)
if grid[m - 1][col] == 0:
dfs(m - 1, col)
for i in range(m):
for j in range(n):
if grid[i][j] == 0:
dfs(i, j)
ans += 1
return ans
3 单调栈
3.1 基础知识
单调栈就是 栈内元素单调递增或者单调递减 的栈,单调栈只能在栈顶操作。
单调栈 >>> 一般只处理 Next Greater Element
问题 >>>> 达到线性复杂度
- 单调栈, 单调队列并非一种数据结构, 而是利用单调性排除冗余 >>>> 对算法进行优化
单调栈的维护是 O(n) 级的时间复杂度,因为所有元素只会进入栈一次,并且出栈后再也不会进栈了。
单调栈的性质:
1.单调栈里的元素具有单调性
2.元素加入栈前,会在栈顶端把破坏栈单调性的元素都删除 ---- 直到满足单调
3.使用单调栈可以找到元素向左遍历第一个比他小的元素,也可以找到元素向左遍历第一个比他大的元素。
3.2 相关题目.
3.2.1 496 . 下一个更大元素 I
- 思路: 利用
nums2
维护一个单调递减栈stack
, 并记录令stack[-1]
出栈的num
class Solution:
def nextGreaterElement(self, nums1: List[int], nums2: List[int]) -> List[int]:
ans, stack,d = [], [], {}
for num in nums2:
while stack and stack[-1] < num:
d[stack[-1]] = num
stack.pop()
stack.append(num)
for num in nums1:
ans.append(d.get(num, -1))
return ans
3.2.2 84 . 柱状图中最大的矩形
- 考虑如果矩形高度单调递增, 每一个高度对应最大矩形面积 >>>> 矩形高度向右扩展宽度
- 若出现破坏单调性 >>>> 最后一个矩形(栈顶) 高度确定 >>> 其对应最大矩形面积确定 >>> 累加宽度(宽度留给之前矩形计算面积) >>>> 栈顶(for到目前最大高度的矩形) 出栈 >>> 直到矩形的高度满足单调位置 >>>> go on for
- 思路 ------------- 单调栈:
class Solution:
def largestRectangleArea(self, heights: List[int]) -> int:
stack, ans, heights = [], 0, heights + [0]
for i in heights:
accumulated_wide = 0
while stack and (i < stack[-1][1]): # stack[:][1]存高度 stack[:][0]存宽度
accumulated_wide += stack[-1][0]
ans = max(ans, accumulated_wide * stack[-1][1])
stack.pop()
stack.append([accumulated_wide + 1, i])
return ans