20220114_datawhale33期_leetcode刷题_6栈堆

六 栈堆

请添加图片描述Images Source: https://realpython.com/

目录

来源

Datewhale33期__LeetCode 刷题 :

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记忆化搜索均可

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 . 柱状图中最大的矩形
  1. 考虑如果矩形高度单调递增, 每一个高度对应最大矩形面积 >>>> 矩形高度向右扩展宽度
  2. 若出现破坏单调性 >>>> 最后一个矩形(栈顶) 高度确定 >>> 其对应最大矩形面积确定 >>> 累加宽度(宽度留给之前矩形计算面积) >>>> 栈顶(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
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值