[leetcode·回溯算法]回溯算法解题套路框架

本文参考labuladong算法笔记[回溯算法解题套路框架 | labuladong 的算法笔记]

本文解决几个问题:

回溯算法是什么?解决回溯算法相关的问题有什么技巧?如何学习回溯算法?回溯算法代码是否有规律可循?

其实回溯算法和我们常说的 DFS 算法基本可以认为是同一种算法,它们的细微差异我在 关于 DFS 和回溯算法的若干问题 中有详细解释,本文聚焦回溯算法,不展开。

抽象地说,解决一个回溯问题,实际上就是遍历一棵决策树的过程,树的每个叶子节点存放着一个合法答案。你把整棵树遍历一遍,把叶子节点上的答案都收集起来,就能得到所有的合法答案

站在回溯树的一个节点上,你只需要思考 3 个问题:

1、路径:也就是已经做出的选择。

2、选择列表:也就是你当前可以做的选择。

3、结束条件:也就是到达决策树底层,无法再做选择的条件。

如果你不理解这三个词语的解释,没关系,我们后面会用「全排列」这个经典的回溯算法问题来帮你理解这些词语是什么意思,现在你先留着印象。

回溯算法框架:

result = []
def backtrack(路径, 选择列表):if 满足结束条件:result.add(路径)returnfor 选择 in 选择列表:做选择backtrack(路径, 选择列表)撤销选择

其核心就是 for 循环里面的递归,在递归调用之前「做选择」,在递归调用之后「撤销选择」,特别简单

全排列问题解析

力扣第 46 题「全排列」就是给你输入一个数组 nums,让你返回这些数字的全排列。

46. 全排列

给定一个不含重复数字的数组 nums ,返回其 所有可能的全排列 。你可以 按任意顺序 返回答案。

示例 1:

输入:nums = [1,2,3]
输出:[[1,2,3],[1,3,2],[2,1,3],[2,3,1],[3,1,2],[3,2,1]]

示例 2:

输入:nums = [0,1]
输出:[[0,1],[1,0]]

示例 3:

输入:nums = [1]
输出:[[1]]

提示:

  • 1 <= nums.length <= 6
  • -10 <= nums[i] <= 10
  • nums 中的所有整数 互不相同

我们这次讨论的全排列问题不包含重复的数字,包含重复数字的扩展场景我在后文 回溯算法秒杀排列组合子集的九种题型 中讲解

另外,有些读者之前看过的全排列算法代码可能是那种 swap 交换元素的写法,和我在本文介绍的代码不同。这是回溯算法两种穷举思路,我会在后文 球盒模型:回溯算法穷举的两种视角 讲明白。现在还不适合直接跟你讲那个解法,你照着我的思路学习即可。

我们在高中的时候就做过排列组合的数学题,我们也知道 n 个不重复的数,全排列共有 n! 个。那么我们当时是怎么穷举全排列的呢?

比方说给三个数 [1,2,3],你肯定不会无规律地乱穷举,一般是这样:

先固定第一位为 1,然后第二位可以是 2,那么第三位只能是 3;然后可以把第二位变成 3,第三位就只能是 2 了;然后就只能变化第一位,变成 2,然后再穷举后两位……

其实这就是回溯算法,我们高中无师自通就会用,或者有的同学直接画出如下这棵回溯树:

只要从根遍历这棵树,记录路径上的数字,其实就是所有的全排列。我们不妨把这棵树称为回溯算法的「决策树」

为啥说这是决策树呢,因为你在每个节点上其实都在做决策。比如说你站在下图的红色节点上:

你现在就在做决策,可以选择 1 那条树枝,也可以选择 3 那条树枝。为啥只能在 1 和 3 之中选择呢?因为 2 这个树枝在你身后,这个选择你之前做过了,而全排列是不允许重复使用数字的。

现在可以解答开头的几个名词:[2] 就是「路径」,记录你已经做过的选择;[1,3] 就是「选择列表」,表示你当前可以做出的选择;「结束条件」就是遍历到树的底层叶子节点,这里也就是选择列表为空的时候

如果明白了这几个名词,可以把「路径」和「选择」列表作为决策树上每个节点的属性,比如下图列出了几个蓝色节点的属性:

我们定义的 backtrack 函数其实就像一个指针,在这棵树上游走,同时要正确维护每个节点的属性,每当走到树的底层叶子节点,其「路径」就是一个全排列

再进一步,如何遍历一棵树?这个应该不难吧。回忆一下之前 学习数据结构的框架思维 写过,各种搜索问题其实都是树的遍历问题,而多叉树的遍历框架就是这样:

def traverse(root: TreeNode):for child in root.children:# 前序位置需要的操作traverse(child)# 后序位置需要的操作

而所谓的前序遍历和后序遍历,他们只是两个很有用的时间点,我给你画张图你就明白了:

前序遍历的代码在进入某一个节点之前的那个时间点执行,后序遍历代码在离开某个节点之后的那个时间点执行

回想我们刚才说的,「路径」和「选择」是每个节点的属性,函数在树上游走要正确处理节点的属性,那么就要在这两个特殊时间点搞点动作:

现在,你是否理解了回溯算法的这段核心框架?

for 选择 in 选择列表:# 做选择将该选择从选择列表移除路径.add(选择)backtrack(路径, 选择列表)# 撤销选择路径.remove(选择)将该选择再加入选择列表

我们只要在递归之前做出选择,在递归之后撤销刚才的选择,就能正确得到每个节点的选择列表和路径。

Python解法代码:

from typing import Listclass Solution:def __init__(self):self.res = []# 主函数,输入一组不重复的数字,返回它们的全排列def permute(self, nums: List[int]) -> List[List[int]]:# 记录「路径」track = []# 「路径」中的元素会被标记为 true,避免重复使用used = [False] * len(nums)self.backtrack(nums, track, used)return self.res# 路径:记录在 track 中# 选择列表:nums 中不存在于 track 的那些元素(used[i] 为 false)# 结束条件:nums 中的元素全都在 track 中出现def backtrack(self, nums: List[int], track: List[int], used: List[bool]):# 触发结束条件if len(track) == len(nums):self.res.append(track.copy())returnfor i in range(len(nums)):# 排除不合法的选择if used[i]: # nums[i] 已经在 track 中,跳过continue# 做选择track.append(nums[i])used[i] = True# 进入下一层决策树self.backtrack(nums, track, used)# 取消选择track.pop()used[i] = False

我们这里稍微做了些变通,没有显式记录「选择列表」,而是通过 used 数组排除已经存在 track 中的元素,从而推导出当前的选择列表:

至此,我们就通过全排列问题详解了回溯算法的底层原理。当然,这个算法解决全排列不是最高效的,你可能看到有的解法连 used 数组都不使用,通过交换元素达到目的。但是那种解法稍微难理解一些,我会在 球盒模型:回溯算法两种穷举视角 中介绍。

但是必须说明的是,不管怎么优化,都符合回溯框架,而且时间复杂度都不可能低于 O(N!),因为穷举整棵决策树是无法避免的,你最后肯定要穷举出 N! 种全排列结果。

这也是回溯算法的一个特点,不像动态规划存在重叠子问题可以优化,回溯算法就是纯暴力穷举,复杂度一般都很高

最后总结

回溯算法就是个多叉树的遍历问题,关键就是在前序遍历后序遍历的位置做一些操作。

写 backtrack 函数时,需要维护走过的「路径」和当前可以做的「选择列表」,当触发「结束条件」时,将「路径」记入结果集

其实想想看,回溯算法和动态规划是不是有点像呢?我们在动态规划系列文章中多次强调,动态规划的三个需要明确的点就是「状态」「选择」和「base case」,是不是就对应着走过的「路径」,当前的「选择列表」和「结束条件」?

动态规划和回溯算法底层都把问题抽象成了树的结构,但这两种算法在思路上是完全不同的。

leetcode练习

数独游戏

大家应该都玩过数独游戏,就是给你一个 9x9 的棋盘,其中有一些格子预先填入了数字,让你在其余空的格子中填入数字 1~9,要求每行、每列和每个 3x3 的九宫格内数字都不能重复。

下面是一个数独游戏的例子(来源 Wikipedia):

我小的时候也尝试过玩数独游戏,但只要稍微有些难度,就搞不定了。后来我才知道做数独是有技巧的,有一些比较专业的数独游戏软件会教你玩数独的技巧,不过在我看来这些技巧都太复杂,根本就没有兴趣看下去。

现在学习了回溯算法,多困难的数独问题都拦不住我了。只要有规则,就一定可以暴力穷举出符合条件的答案来,不是吗?

N 皇后问题

在国际象棋中,皇后可以攻击同一行、同一列和同一条对角线上的任意单位。N 皇后问题是指在一个 N×N 的棋盘上摆放 N 个皇后,要求任何两个皇后之间都不能互相攻击。

换句话说,就是让你在一个 N×N 的棋盘上放置 N 个皇后,使得每行、每列和每个对角线都只有一个皇后。

比如这是 8 皇后问题的一个解(来源 Wikipedia):

可以看到,对于任意一个皇后,它所在的行、列和对角线(左上、右上、左下、右下)都没有其他皇后,所以这就是一个符合规则的解。

在讲上述题目之前,我需要先讲一道比较简单的回溯算法问题,把这个问题作为铺垫,就能更容易理解数独游戏和 N 皇后问题的解法了。

n 位二进制数的所有可能

我来给你编一道简单的题目,请你实现这样一个函数:

def generateBinaryNumber(n: int) -> List[str]:

函数的输入是一个正整数 n,请你返回所有长度为 n 的二进制数(0、1 组成),你可以按任意顺序返回答案。

比如说 n = 3,那么你需要以字符串形式返回如下 2^3=8种不同的二进制数:

000
001
010
011
100
101
110
111

这道题可以认为是数独游戏和 N 皇后问题的简化版:

这道题相当于让你对一个长度为 n 的一维数组中的每一个位置进行穷举,其中每个位置可以填 0 或 1本质还是遍历一颗二叉树。

数独游戏相当于让你对一个 9x9 的二维数组中的每个位置进行穷举,其中每个位置可以是数字 1~9,且同一行、同一列和同一个 3x3 的九宫格内数字不能重复。本质是遍历一颗九叉树。

N 皇后问题相当于让你对一个 N x N 的二维数组中的每个位置进行穷举,其中每个位置可以不放皇后或者放置皇后(相当于 0 或 1),且不能存在多个皇后在同一行、同一列或同一对角线上。本质是遍历一颗N叉树。

所以,只要你把这道简化版的题目的穷举过程搞明白,其他问题都迎刃而解了,无非是规则多了一些而已。

思路分析

题目是对一个长度为 n 的数组进行穷举,每个位置可以填入 0 或 1,让你返回所有可能的结果。

Important

看到这种题目,脑海中应该立刻出现一棵递归树;有了这棵树,你就有办法使用 回溯算法框架 无遗漏地穷举所有可能的解;能无遗漏地穷举所有解,就一定能找到符合题目要求的答案

具体来说,这道题的递归树应该是一棵 n + 1 层的二叉树。

递归树的节点就是穷举过程中产生的状态,以不同的视角来看这些节点,会有不同的作用。

具体来说就是以下问题:

应该如何理解这棵树每一层的节点;应该如何理解这棵树每一列(每条树枝)的节点;应该如何理解每个节点本身

你可以把鼠标移动到可视化面板生成的这棵递归树的每个节点上,会显示节点和树枝的信息。请你结合可视化面板的这些信息,听我来分析:

1、从层的角度来看,第 i 层的节点表示的就是长度为 i 的所有二进制数(i 从 0 开始算),比如第 2 层的节点分别是 00011011,这些就是长度为 2 的所有二进制数。

2、从列(树枝)的角度来看,每条树枝上的节点记录了状态转移的过程。比如你把鼠标移动到 001 这个节点上,树枝上会显示这个 001 是如何被穷举出来的,即首先选择 0,然后选择 0,最后选择 1

3、从节点的角度来看,每个节点都是一个状态,从该节点向下延伸的树枝,就是当前状态的所有可能的选择。比如第二层的节点 01,它的前两位已经确定,现在要对第三位进行穷举。第三位可以是 0 或 1,所以从 01 这个节点向下延伸的两个子节点就是 010 和 011

回溯算法代码框架

class Solution:# 生成所有长度为 n 的二进制数def generateBinaryNumber(self, n: int) -> List[str]:res = []# 回溯路径track = []# 回溯算法核心框架def backtrack(i):if i == n:# 当递归深度等于 n 时,说明找到一个长度为 n 的二进制数,结束递归res.append(''.join(track))return# 选择有两种,0 或 1# 所以递归树是一棵二叉树for val in [0, 1]:# 做选择track.append(str(val))backtrack(i + 1)# 撤销选择track.pop()backtrack(0)return res

37. 解数独 | 力扣 | LeetCode |  🔴

编写一个程序,通过填充空格来解决数独问题。

数独的解法需 遵循如下规则

  1. 数字 1-9 在每一行只能出现一次。
  2. 数字 1-9 在每一列只能出现一次。
  3. 数字 1-9 在每一个以粗实线分隔的 3x3 宫内只能出现一次。(请参考示例图)

数独部分空格内已填入了数字,空白格用 '.' 表示。

示例 1:

输入:board = [["5","3",".",".","7",".",".",".","."],["6",".",".","1","9","5",".",".","."],[".","9","8",".",".",".",".","6","."],["8",".",".",".","6",".",".",".","3"],["4",".",".","8",".","3",".",".","1"],["7",".",".",".","2",".",".",".","6"],[".","6",".",".",".",".","2","8","."],[".",".",".","4","1","9",".",".","5"],[".",".",".",".","8",".",".","7","9"]]
输出:[["5","3","4","6","7","8","9","1","2"],["6","7","2","1","9","5","3","4","8"],["1","9","8","3","4","2","5","6","7"],["8","5","9","7","6","1","4","2","3"],["4","2","6","8","5","3","7","9","1"],["7","1","3","9","2","4","8","5","6"],["9","6","1","5","3","7","2","8","4"],["2","8","7","4","1","9","6","3","5"],["3","4","5","2","8","6","1","7","9"]]
解释:输入的数独如上图所示,唯一有效的解决方案如下所示:

提示:

  • board.length == 9
  • board[i].length == 9
  • board[i][j] 是一位数字或者 '.'
  • 题目数据 保证 输入数独仅有一个解

先不管数独游戏的规则,反正现在就是有 9x9=81 个格子,每个格子都可以填入 1~9 对吧,那总共就有 981981 种填法,把这些解法都穷举一遍,一定可以找到符合规则的答案。

对于这个问题,你心里应该抽象出一棵九叉递归树,共有 81 层,只要心里有了这棵树,就可以参照前面实现的 generateBinaryNumber 函数写出求解数独游戏的代码了。

不过你可能会有以下疑惑:

1、前面 generateBinaryNumber 是针对一维数组的穷举,如何才能适配到数独游戏的二维数组呢?

2、981981 是一个非常大的数字,穷举这么多递归树节点,即便写出回溯算法代码,也会超时吧?

3、generateBinaryNumber 函数返回的是所有解,而数独题目只要求找到一个可行解,所以当我们找到一个可行解时就应该结束算法,这样可以减少时间复杂度。

我来一一解答。

对于第一个问题,不熟悉递归的读者可能会有这个疑问,是不是在二维数组上递归就把你绕晕了?那么我们干脆玩个花样,直接绕过二维数组的问题,因为不管是几维的数组,都可以转化成一维数组

说白了,就是设计一个编解码算法,能够把一个多维坐标(多元组)编码到一个一维坐标(数字),且可以反向解码回去。

对于一个 MxN 的二维数组,可以把二维坐标 (i, j) 映射到一维坐标 index = i * N + j,反之可以通过 i = index / N 和 j = index % N 得到原来的二维坐标。

有了这个编解码算法,数独游戏的 9x9 二维数组就可以在逻辑上看做一个长度为 81 的一维数组,然后按照一维数组的递归树来穷举即可。

对于第二个问题,981981 只是一个非常粗略的估算,实际写代码时,要考虑数独的规则。

首先,初始棋盘会给你预先填充一些数字,这些数字是不能改变的,所以这些格子不用穷举,应该从 81 个格子去除。

另外,数独规则要求每行、每列和每个 3x3 的九宫格内数字都不能重复,所以实际上每个空格子可以填入的数字并不是真的有 9 种选择,而是要根据当前棋盘的状态,去除不合法的数字(剪枝)。

而且,981981 是穷举所有可行解的复杂度估算,实际上题目只要求我们寻找一个可行解,所以只要找到一个可行解就可以结束算法了,没必要穷举所有解。

那么这样算下来,穷举的实际复杂度取决于空格子的个数,远低于 981981,所以不用担心超时问题。

讲到这里,我们应该可以发现一个有趣的现象:

按照人类的一般理解,规则越多,问题越难;但是对于计算机来说,规则越多,问题反而越简单。

因为它要穷举嘛,规则越多,越容易剪枝,搜索空间就越小,就越容易找到答案。反之,如果没有任何规则,那就是纯暴力穷举,效率会非常低。

我们对回溯算法的剪枝优化,本质上就是寻找规则,提前排除不合法的选择,从而提高穷举的效率。

对于第三个问题,让算法在找到一个可行解时立即结束递归。这个问题有多种解决方案,我会建议使用一个全局变量来标记是否找到可行解(原因见 解答回溯/DFS的若干问题),辅助递归算法提前终止。

回溯算法代码实现:

class Solution:# 标记是否已经找到可行解found = Falsedef solveSudoku(self, board):self.backtrack(board, 0)# 路径:board 中小于 index 的位置所填的数字# 选择列表:数字 1~9# 结束条件:整个 board 都填满数字def backtrack(self, board, index):if self.found:# 已经找到一个可行解,立即结束returnm, n = 9, 9i, j = index // n, index % nif index == m * n:# 找到一个可行解,触发 base caseself.found = Truereturnif board[i][j] != '.':# 如果有预设数字,不用我们穷举self.backtrack(board, index + 1)returnfor ch in '123456789':# 剪枝:如果遇到不合法的数字,就跳过if not self.isValid(board, i, j, ch):continue# 做选择board[i][j] = chself.backtrack(board, index + 1)if self.found:# 如果找到一个可行解,立即结束# 不要撤销选择,否则 board[i][j] 会被重置为 '.'return# 撤销选择board[i][j] = '.'# 判断是否可以在 (r, c) 位置放置数字 numdef isValid(self, board, r, c, num):for i in range(9):# 判断行是否存在重复if board[r][i] == num:return False# 判断列是否存在重复if board[i][c] == num:return False# 判断 3 x 3 方框是否存在重复if board[(r // 3) * 3 + i // 3][(c // 3) * 3 + i % 3] == num:return Falsereturn True

51. N 皇后 | 力扣 | LeetCode |  🔴

按照国际象棋的规则,皇后可以攻击与之处在同一行或同一列或同一斜线上的棋子。

n 皇后问题 研究的是如何将 n 个皇后放置在 n×n 的棋盘上,并且使皇后彼此之间不能相互攻击。

给你一个整数 n ,返回所有不同的 n 皇后问题 的解决方案。

每一种解法包含一个不同的 n 皇后问题 的棋子放置方案,该方案中 'Q' 和 '.' 分别代表了皇后和空位。

示例 1:

输入:n = 4
输出:[[".Q..","...Q","Q...","..Q."],["..Q.","Q...","...Q",".Q.."]]
解释:如上图所示,4 皇后问题存在两个不同的解法。

示例 2:

输入:n = 1
输出:[["Q"]]

提示:

  • 1 <= n <= 9

其实 N 皇后问题和数独游戏的解法思路是一样的,具体有以下差别:

1、数独的规则和 N 皇后问题的规则不同,我们需要修改 isValid 函数,判断一个位置是否可以放置皇后。

2、题目要求找到 N 皇后问题所有解,而不是仅仅找到一个解。我们需要去除算法提前终止的逻辑,让算法穷举并记录所有的合法解。

这两个问题都比较容易解决,按理说可以直接参照数独游戏的代码实现 N 皇后问题。

但 N 皇后问题相对数独游戏还有一个优化:我们可以以为单位进行穷举,而不是像数独游戏那样以格子为单位穷举

举个直观的例子,在数独游戏中,如果我们设置 board[i][j] = 1,接下来呢,要去穷举 board[i][j+1] 了对吧?而对于 N 皇后问题,我们知道每行必然有且只有一个皇后,所以如果我们决定在 board[i] 这一行的某一列放置皇后,那么接下来就不用管 board[i] 这一行了,应该考虑 board[i+1] 这一行的皇后要放在哪里。

所以 N 皇后问题的穷举对象是棋盘中的行,每一行都持有一个皇后,可以选择把皇后放在该行的任意一列。

回溯算法代码实现:

class Solution:def __init__(self):self.res = []def solveNQueens(self, n: int) -> list[list[str]]:# 每个字符串代表一行,字符串列表代表一个棋盘# '.' 表示空,'Q' 表示皇后,初始化空棋盘board = ["." * n for _ in range(n)]self.backtrack(board, 0)return self.res# 路径:board 中小于 row 的那些行都已经成功放置了皇后# 选择列表:第 row 行的所有列都是放置皇后的选择# 结束条件:row 超过 board 的最后一行def backtrack(self, board: list[str], row: int):if row == len(board):# 棋盘已经填满,找到一个解self.res.append(board[:])returnn = len(board[row])for col in range(n):# 剪枝,排除不合法选择if not self.isValid(board, row, col):continue# 做选择board[row] = board[row][:col] + 'Q' + board[row][col+1:]# 进入下一行决策self.backtrack(board, row + 1)# 撤销选择board[row] = board[row][:col] + '.' + board[row][col+1:]# 是否可以在 board[row][col] 放置皇后?def isValid(self, board: list[str], row: int, col: int) -> bool:n = len(board)# 因为我们是从上往下放置皇后的,# 所以只需要检查上方是否有皇后互相冲突,# 不需要检查下方# 检查列是否有皇后互相冲突for i in range(row):if board[i][col] == 'Q':return False# 检查右上方是否有皇后互相冲突i, j = row - 1, col + 1while i >= 0 and j < n:if board[i][j] == 'Q':return Falsei, j = i - 1, j + 1# 检查左上方是否有皇后互相冲突i, j = row - 1, col - 1while i >= 0 and j >= 0:if board[i][j] == 'Q':return Falsei, j = i - 1, j - 1return True

52. N 皇后 II | 力扣 | LeetCode |  🔴

n 皇后问题 研究的是如何将 n 个皇后放置在 n × n 的棋盘上,并且使皇后彼此之间不能相互攻击。

给你一个整数 n ,返回 n 皇后问题 不同的解决方案的数量。

示例 1:

输入:n = 4
输出:2
解释:如上图所示,4 皇后问题存在两个不同的解法。

示例 2:

输入:n = 1
输出:1

提示:

  • 1 <= n <= 9

直接把 res 变量变成 int 类型,每次在 base case 找到一个合法答案的时候递增 res 变量即可:

class Solution:def __init__(self):# 仅仅记录合法结果的数量self.res = 0def backtrack(self, board: list[str], row: int):if row == len(board):# 找到一个合法结果self.res += 1return# 其他都一样

总结

回溯算法本质上就是遍历一颗多叉树:

1、确定递归终止条件

2、执行单层操作逻辑

3、确定下一层递归所需的操作列表

4、注意剪枝和代码优化

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/12430.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

SQL Server中RANK()函数:处理并列排名与自然跳号

RANK()是SQL Server的窗口函数&#xff0c;为结果集中的行生成排名。当出现相同值时&#xff0c;后续排名会跳过被占用的名次&#xff0c;形成自然间隔。与DENSE_RANK()的关键区别在于是否允许排名值连续。 语法&#xff1a; RANK() OVER ([PARTITION BY 分组列]ORDER BY 排序…

多线程的常用方法

getName和setName方法 注意点 setName方法最好放在线程启动之前 最好在线程启动之前修改名字&#xff0c;因为线程启动之后&#xff0c;如果执行过快的话&#xff0c;那么在调用 setName() 之前线程可能就已经结束了 MyThread t1 new MyThread("haha"); t1.setNa…

Unity游戏(Assault空对地打击)开发(6) 鼠标光标的隐藏

前言 鼠标光标在游戏界面太碍眼了&#xff0c;要隐藏掉。 详细操作 新建一个脚本HideCursor&#xff0c;用于隐藏光标。 写入以下代码。 意义&#xff1a;游戏开始自动隐藏光标&#xff0c;按Esc&#xff08;显示<-->隐藏&#xff09;。 using System.Collections; using…

【Linux系统】信号:再谈OS与内核区、信号捕捉、重入函数与 volatile

再谈操作系统与内核区 1、浅谈虚拟机和操作系统映射于地址空间的作用 我们调用任何函数&#xff08;无论是库函数还是系统调用&#xff09;&#xff0c;都是在各自进程的地址空间中执行的。无论操作系统如何切换进程&#xff0c;它都能确保访问同一个操作系统实例。换句话说&am…

冰蝎v4.0.5 来啦

webshell始终是渗透测试的热门&#xff0c;上次护网写冰蝎检测规则&#xff0c;加密流量&#xff0c;有点压力&#xff0c;今天终于有空来复现一下&#xff0c;我知道玩知乎的大佬很多&#xff0c;轻一点喷&#xff0c;学习新知识不丢人&#xff5e; ailx10 1949 次咨询 4.9 …

WPS怎么使用latex公式?

1、下载并安装mathtype https://blog.csdn.net/weixin_43135178/article/details/125143654?sharetypeblogdetail&sharerId125143654&sharereferPC&sharesourceweixin_43135178&spm1011.2480.3001.8118 2、将mathtype嵌入在WPS MathType面板嵌入器,免费工具…

基于微信小程序的私家车位共享系统设计与实现(LW+源码+讲解)

专注于大学生项目实战开发,讲解,毕业答疑辅导&#xff0c;欢迎高校老师/同行前辈交流合作✌。 技术范围&#xff1a;SpringBoot、Vue、SSM、HLMT、小程序、Jsp、PHP、Nodejs、Python、爬虫、数据可视化、安卓app、大数据、物联网、机器学习等设计与开发。 主要内容&#xff1a;…

安全策略配置

需求: 1、VLAN 2属于办公区;VLAN 3属于生产区 2、办公区PC在工作日时间(周一至周五&#xff0c;早8到晚6)可以正常访问0A Server&#xff0c;其他时间不允许 3、办公区PC可以在任意时刻访问web server 4、生产区PC可以在任意时刻访问0A Server&#xff0c;但是不能访问Web serv…

【大数据技术】教程05:本机DataGrip远程连接虚拟机MySQL/Hive

本机DataGrip远程连接虚拟机MySQL/Hive datagrip-2024.3.4VMware Workstation Pro 16CentOS-Stream-10-latest-x86_64-dvd1.iso写在前面 本文主要介绍如何使用本机的DataGrip连接虚拟机的MySQL数据库和Hive数据库,提高编程效率。 安装DataGrip 请按照以下步骤安装DataGrip软…

响应式编程_01基本概念:前世今生

文章目录 引言响应式编程的技术优势全栈式响应式编程从传统开发模式到异步执行技术Web 请求与 I/O 模型异步调用的实现技术回调Future机制 响应式编程实现方法观察者模式发布-订阅模式数据流与响应式 响应式宣言和响应式系统 引言 大流量、高并发的访问请求的项目&#xff0c;…

龙芯+FreeRTOS+LVGL实战笔记(新)——16数码管驱动

本专栏是笔者另一个专栏《龙芯+RT-Thread+LVGL实战笔记》的姊妹篇,主要的区别在于实时操作系统的不同,章节的安排和任务的推进保持一致,并对源码做了完善与优化,各位可以前往本人在B站的视频合集(图1所示)观看所有演示视频,合集首个视频链接为: https://www.bilibili.…

正态分布和标准正态分布区别与联系(复习)

1)区别&#xff1a;正态分布的平均数为μ&#xff0c;标准差为σ&#xff1b;不同的正态分布可能有不同的μ值和σ值&#xff0c;正态分布曲线形态因此不同。 标准正态分布平均数μ0&#xff0c;标准差σ1&#xff0c;μ和σ都是固定值&#xff1b;标准正态分布曲线形态固定。…

Airflow:深入理解Apache Airflow Task

Apache Airflow是一个开源工作流管理平台&#xff0c;支持以编程方式编写、调度和监控工作流。由于其灵活性、可扩展性和强大的社区支持&#xff0c;它已迅速成为编排复杂数据管道的首选工具。在这篇博文中&#xff0c;我们将深入研究Apache Airflow 中的任务概念&#xff0c;探…

Golang 并发机制-5:详解syn包同步原语

并发性是现代软件开发的一个基本方面&#xff0c;Go&#xff08;也称为Golang&#xff09;为并发编程提供了一组健壮的工具。Go语言中用于管理并发性的重要包之一是“sync”包。在本文中&#xff0c;我们将概述“sync”包&#xff0c;并深入研究其最重要的同步原语之一&#xf…

走向基于大语言模型的新一代推荐系统:综述与展望

HightLight 论文题目&#xff1a;Towards Next-Generation LLM-based Recommender Systems: A Survey and Beyond作者机构&#xff1a;吉林大学、香港理工大学、悉尼科技大学、Meta AI论文地址&#xff1a; https://arxiv.org/abs/2410.1974 基于大语言模型的下一代推荐系统&…

LabVIEW微位移平台位移控制系统

本文介绍了基于LabVIEW的微位移平台位移控制系统的研究。通过设计一个闭环控制系统&#xff0c;针对微位移平台的通信驱动问题进行了解决&#xff0c;并提出了一种LabVIEW的应用方案&#xff0c;用于监控和控制微位移平台的位移&#xff0c;从而提高系统的精度和稳定性。 项目背…

list容器(详解)

list的介绍及使用&#xff08;了解&#xff0c;后边细讲&#xff09; 1.1 list的介绍&#xff08;双向循环链表&#xff09; https://cplusplus.com/reference/list/list/?kwlist&#xff08;list文档介绍&#xff09; 1. list是可以在常数范围内在任意位置进行插入和删除的序…

昆仑万维Java开发面试题及参考答案

进程和线程的区别是什么? 进程和线程都是操作系统中非常重要的概念,它们在多个方面存在显著的区别。 从定义上看,进程是操作系统进行资源分配和调度的基本单位。每个进程都有自己独立的内存空间,包括代码段、数据段、堆栈段等。例如,当你在电脑上同时打开浏览器和音乐播放…

系统学习算法:专题九 穷举vs暴搜vs深搜vs回溯vs剪枝

其中标题的深搜&#xff0c;回溯&#xff0c;剪枝我们之前专题都已经有过学习和了解&#xff0c;这里多了两个穷举和暴搜&#xff0c;其实意思都差不多&#xff0c;穷举就是穷尽力气将所有情况都列举出来&#xff0c;暴搜就是暴力地去一个一个情况搜索&#xff0c;所以就是全部…

人类心智逆向工程:AGI的认知科学基础

文章目录 引言:为何需要逆向工程人类心智?一、逆向工程的定义与目标1.1 什么是逆向工程?1.2 AGI逆向工程的核心目标二、认知科学的四大支柱与AGI2.1 神经科学:大脑的硬件解剖2.2 心理学:心智的行为建模2.3 语言学:符号与意义的桥梁2.4 哲学:意识与自我模型的争议三、逆向…