【算法】回溯算法(三):三记重锤与 N 皇后——记忆化、状态设计与三层漏斗

【算法】回溯算法(三):三记重锤与 N 皇后------记忆化、状态设计与三层漏斗

摘要

回溯系列第三篇,收官。上一篇以 LC842 验收"毕业",我以为真毕业了------接下来三记重锤锤醒了梦:记忆化的三次错位 (417 万次调用 vs 176 次,缓存选择、缓存候选、只写不读一路踩过去)、排序被禁后的去重 (层属性 vs 路径属性,usedThisLevel 不还原是精髓)、中间态失控 (把 Go 的栈撑爆 1GB)。三锤全部指向同一件事:状态设计 。最后用 N 皇后收官(对角线编号 r±c + 三个标记数组 + 四动作对),并把整个系列沉淀成三层漏斗识别框架和写题前的终极检查清单。本篇也是通往动态规划的桥------记忆化的判据,就是 DP 的入场券。

说明,以下环境基于 go1.21+,题目均来自 LeetCode。前置阅读:回溯算法(一):从一道 IP 题到万能模板回溯算法(二):五个坑与一次验收。配套代码仓库(按题号分目录):https://github.com/a18792721831/studyleetCode

系列篇目:

【算法】回溯算法(三):三记重锤与 N 皇后------记忆化、状态设计与三层漏斗

  • [【算法】回溯算法(三):三记重锤与 N 皇后------记忆化、状态设计与三层漏斗](#【算法】回溯算法(三):三记重锤与 N 皇后——记忆化、状态设计与三层漏斗)
    • 摘要
    • [1. 翻车六:记忆化的三次错位(LC139 超时 → LC140 两连坑)](#1. 翻车六:记忆化的三次错位(LC139 超时 → LC140 两连坑))
      • [1.1 第一次错位:不知道要记忆](#1.1 第一次错位:不知道要记忆)
      • [1.2 第二次错位:memo 存了"一个选择"](#1.2 第二次错位:memo 存了"一个选择")
      • [1.3 第三次错位:memo 存了"候选词列表"](#1.3 第三次错位:memo 存了"候选词列表")
      • [1.4 判据:缓存结论,不缓存选择](#1.4 判据:缓存结论,不缓存选择)
    • [2. 翻车七:排序被禁之后(LC491 递增子序列)](#2. 翻车七:排序被禁之后(LC491 递增子序列))
      • [2.1 排序的真正角色](#2.1 排序的真正角色)
      • [2.2 层属性 vs 路径属性](#2.2 层属性 vs 路径属性)
      • [2.3 去重四件套集齐](#2.3 去重四件套集齐)
    • [3. 翻车八:中间态失控(LC79 单词搜索)](#3. 翻车八:中间态失控(LC79 单词搜索))
      • [3.1 病根:移动和匹配被拆成了两个递归](#3.1 病根:移动和匹配被拆成了两个递归)
      • [3.2 修复:移动即匹配,消灭中间态](#3.2 修复:移动即匹配,消灭中间态)
      • [3.3 粒度的两个方向](#3.3 粒度的两个方向)
      • [3.4 决策设计四检验(三验证升级版)](#3.4 决策设计四检验(三验证升级版))
    • [4. 状态设计收官:N 皇后(LC51)](#4. 状态设计收官:N 皇后(LC51))
      • [4.1 骨架:每层一行,一次调用](#4.1 骨架:每层一行,一次调用)
      • [4.2 对角线的数学:这题的灵魂](#4.2 对角线的数学:这题的灵魂)
      • [4.3 三个标记数组 + 四动作对](#4.3 三个标记数组 + 四动作对)
      • [4.4 "折叠判断"的第四次登场](#4.4 "折叠判断"的第四次登场)
    • [5. 识别框架:三层漏斗](#5. 识别框架:三层漏斗)
      • [5.1 第一层:范式闸门(是不是回溯)](#5.1 第一层:范式闸门(是不是回溯))
      • [5.2 第二层:家族闸门(用哪个骨架)](#5.2 第二层:家族闸门(用哪个骨架))
      • [5.3 第三层:延伸模块扫描(加什么装备)](#5.3 第三层:延伸模块扫描(加什么装备))
      • [5.4 实操流程:迭代式](#5.4 实操流程:迭代式)
    • [6. 终极检查清单](#6. 终极检查清单)
    • 总结
    • 参考资料

1. 翻车六:记忆化的三次错位(LC139 超时 → LC140 两连坑)

1.1 第一次错位:不知道要记忆

我自己写 139 的回溯版,测试用例:130 个 a + b,字典是 aaaaaaaaaaa(10 种长度)。跑了半天跑不完,缩小到 22 个 a 统计调用次数:

复制代码
22 个 a + b:
纯回溯:   4,169,729 次调用
记忆化版:       176 次

417 万 vs 176 。病根:字典有 10 种长度的词,切到位置 k 的路径数指数增长,而所有路径到达同一个位置后做的事情完全一样------"从位置 k 把剩下的拆完"。这个子问题的答案只取决于 k,与怎么到达毫无关系,但纯回溯每条路径到达后都从头完整枚举一遍。

更扎心的是这个用例无解b 匹配不上任何词),找到就短路返回的技巧救不了它------必须把整个搜索空间走完才能确认无解,这时候记忆化是唯一的活路:

go 复制代码
// 三态:0=没算过 1=可拆 2=不可拆(用 bool 无法区分"没算过"和"算出来是 false")
memo := make([]int8, len(s)+1)

1.2 第二次错位:memo 存了"一个选择"

升级打 140 单词拆分 II(要所有方案)。第一版我把 memo 设计成 map[int]string------存"这个位置匹配到的某个词"。收集时还用 return 短路,结果只找到一个方案。

两个错误:

  1. return 短路错位:139 的短路是对的(只要可行性),140 要所有方案------收集完必须继续枚举。同一个"找到就返回",在存在性搜索里是优化,在枚举搜索里是 bug
  2. memo 语义错位 :我存的是"选了哪个词"(过程 ),而防爆炸需要存"从这出发的结论"。前一个相同值没被选(说明它在同层刚被试完弹回),我缓存它的"选择"会让后续所有路径都被锁死在同一个词上

1.3 第三次错位:memo 存了"候选词列表"

第二版我把 memo 升级成 map[int][]string------存"这个位置能匹配的所有词"。正确性达标了,但跑那个无解用例:

复制代码
22 个 a + b:
我的版本: 4,169,729 次调用   ← 和完全没有 memo 一模一样!
DP 可行版:            2 次调用

一个数字都没差 。因为缓存候选词列表后,命中照样要 for 循环递归全部候选------子树还是整棵重搜。省掉的只是"重新扫描词表做字符串匹配"的零钱,指数级的路径枚举一分没省。

1.4 判据:缓存结论,不缓存选择

三次错位之后,判据终于立住了:

缓存的东西,能不能让后来者跳过子树的展开?

  • 候选词列表不能(选择还要逐一展开)
  • can[end] == false 能(整支不进)
  • 句子列表能(直接拼接)
  • 一个词的"选择"根本是错的(锁死分支)

140 的正确姿势是两种之一:先跑 139 的 DP 算出 can[](从 idx 能否拆完),回溯只走活路can[end] 为 false 的分支一步不进);或者 memo 直接缓存子问题的完整产出(map[int][]string 存"从 idx 能拼出的所有句子",递归返回值直接拼接)。

还有一个更深的判据,判断这道题需不需要记忆化------看状态和路径的关系:

  • 路径即状态 (排列/组合/子集):选择序列本身就是状态,[1,2][2,1] 是两个不同状态------每条路径活在不同的状态里,没有重复可折叠,永远不需要 memo
  • 状态与路径分离 (切分/网格/博弈):状态是聚合量(切到哪个位置、走到哪个格子、剩什么局面),多条路径坍缩到同一状态------重复计算出现了,记忆化的靶子

先问"我的状态是什么",再问"不同路径会坍缩到同一状态吗"------会,就必须想记忆化。

2. 翻车七:排序被禁之后(LC491 递增子序列)

递增子序列:找出所有长度 ≥2 的非严格递增子序列,元素必须保持原序 ,含重复元素,解不能重复。这题是块试金石------它禁止排序(排序会摧毁"子序列按原序提取"的语义),而我学的同层去重全部依赖排序。

我第一版叠了五个错误,输出 22 个结果(预期 8),[6,7] 重复 9 次:

  1. 模型混乱:选一个骨架里又塞了显式"不选"分支------for 的 i 递增已经天然表达"跳过",再加显式跳过等于每批子序列枚举两遍
  2. 递归传 start+1 而不是 i+1
  3. 递增约束三处错:len(path)>1 应为非空、break 应为 continue未排序!当前元素小不代表后面没有大的)
  4. 一个 map 想同时干"路径判重"和"同层判重"两种语义
  5. nums[i]==nums[i-1] 相邻判重------依赖排序让相同值相邻,这里失效

2.1 排序的真正角色

撞完墙才想通:排序在同层去重里的唯一角色 = 让相同值相邻,从而用"和前一个比较"这个 O(1) 手段发现"本层又遇到同一个值" 。它从来不是为了去重本身,只是去重的"发现手段"。禁止排序后,发现手段换成哈希集合 ------不依赖相邻,直接查"这个值本层试过没"。本质从没变过:同层同值,只走第一个

2.2 层属性 vs 路径属性

这里出现了一个新的关键区分------两种标记的生命周期:

used 数组(47/90) usedThisLevel 集合(491)
记录什么 下标是否在 path 里 这个本层是否试过
生命周期 路径属性:做选择置 true、还原置 false,随一条路径翻转 层属性 :每次进入函数新建,递归返回后不还原
为什么 它描述"当前路径的状态",路径变了它就变 它描述"这一层 for 循环已经试过哪些值",循环还在本层继续,标记必须保持;下一层递归自己建新的

usedThisLevel 不还原是精髓------递归返回后本层 for 还要继续试 i+1、i+2,"这个值试过了"的记录必须留住。它随层生灭,不需要你还原。

2.3 去重四件套集齐

go 复制代码
func findSubsequences(nums []int) [][]int {
	res := [][]int{}
	path := []int{}
	var backtrack func(start int)
	backtrack = func(start int) {
		// 每节点收集:长度 ≥ 2 的节点就是答案------收集后继续,不 return
		if len(path) >= 2 {
			res = append(res, slices.Clone(path))
		}
		usedThisLevel := map[int]bool{} // 层属性:本层已试过的"值"
		for i := start; i < len(nums); i++ {
			// 合法性:非严格递增。continue 不是 break------没排序!
			if len(path) > 0 && nums[i] < path[len(path)-1] {
				continue
			}
			// 同层去重:这个值本层已经试过
			if usedThisLevel[nums[i]] {
				continue
			}
			usedThisLevel[nums[i]] = true // 标记,不还原(层属性)
			path = append(path, nums[i])
			backtrack(i + 1)
			path = path[:len(path)-1]
		}
	}
	backtrack(0)
	return res
}
47 排列 40 组合 90 子集 491 子序列
骨架 全范围 for + used start 推进 要/不要 start 推进 + 每节点收集
能排序吗 禁止
发现同层同值 相邻比较 相邻比较 相邻比较 层内哈希集合
同层/同枝区分 !used[i-1](前一个已弹出) i > start(前一个是本层候选) !used[i-1] 集合层作用域天然区分

一个原则(同层同值只走第一个)、两个发现手段(排序相邻/层内集合)、三种层界定方式(used 翻转/start 边界/集合作用域)------四道题亲手翻过车修干净,这个考点才算真正焊死。

3. 翻车八:中间态失控(LC79 单词搜索)

这次翻车最壮观------不是超时,是把 Go 的栈撑爆了 1GB

单词搜索:网格里按上下左右找单词,格子不能复用。我第一版设计了一个 step 参数来区分"移动了一步"和"匹配了一个字符",让递归同时承担"在网格上移动"和"匹配字符"两个职责。跑 ABCB 用例,直接:

复制代码
fatal error: stack overflow(goroutine 栈超过 1GB 上限)

main.existUser.func1(0x2, 0x4, {空path}, 0x0, step=2233116)  ← step 涨到 223 万
main.existUser.func1(0x2, 0x3, {空path}, 0x0, step=2233115)
main.existUser.func1(0x1, 0x3, {空path}, 0x0, step=2233114)  ← 在 (1,3)↔(2,3) 之间
main.existUser.func1(0x2, 0x3, ...)                          ← 来回震荡,没有东西拦它

3.1 病根:移动和匹配被拆成了两个递归

我的 step 机制产生了"移动了但还没匹配 "的中间态------空 path 时这个状态没有任何终止条件(没有 visited 拦截、没有步数上限),漫游在网格环上无限转,直到栈爆。

3.2 修复:移动即匹配,消灭中间态

go 复制代码
// 语义:word[idx] 必须在 (r,c) 这个格子上匹配
var dfs func(r, c, idx int) bool
dfs = func(r, c, idx int) bool {
    if r < 0 || r >= m || c < 0 || c >= n {        // 出界
        return false
    }
    if used[r][c] || board[r][c] != word[idx] {     // 已用 / 字母不匹配
        return false
    }
    if idx == len(word)-1 {                          // 最后一个字符匹配上了
        return true
    }
    used[r][c] = true                                // 做选择
    found := dfs(r+1, c, idx+1) || dfs(r-1, c, idx+1) ||
        dfs(r, c+1, idx+1) || dfs(r, c-1, idx+1)     // 四方向 = 匹配下一字符
    used[r][c] = false                               // 还原
    return found
}
// 起点森林在外层:双重循环枚举所有格子作为起点

三参数 (r, c, idx) 的每个状态都是合法局面。向邻居移动一步就是去匹配下一个字符------移动和匹配是原子的,不可分。递归里根本不存在"移动了但没匹配"这种状态。

3.3 粒度的两个方向

有意思的是,这次错误和第一次翻车(括号题)方向正好相反

复制代码
LC22 括号:把多个原子动作打包成一个决策("放 idx 对")→ 粒度过粗 → 缺解
LC79 网格:把一个原子动作劈成两个递归(移动+匹配)    → 粒度过碎 → 中间态失控

方向相反,病根相同:没识别出真正的原子动作。"最小决策"的最小 不是越小越好,而是恰好等于一个不可分割的完整语义单元。代码可分(改坐标和比字符是两行)≠ 语义可分("走一步"的意思就是"去那里匹配下一个字")。

3.4 决策设计四检验(三验证升级版)

复制代码
1. 原子性:这个决策能拆成两个有独立语义的动作吗?
   能 → 切的位置可能错了(LC79 的教训)
2. 中间态合法性:拆开后出现的中间状态,题目语义里存在吗?
   不存在 → 必须合并(LC79:移动+匹配)
3. 覆盖完整性:所有合法解都能被某个决策序列构造出来吗?
   不能 → 粒度过粗(LC22:(()()) 无路径)
4. 约束局部可判:每个决策点的可行性,能用当前状态 O(1) 判断吗?

4. 状态设计收官:N 皇后(LC51)

最后一题,N 皇后。第一版直接崩了:没有递归下沉、对角线检查只排了穿过棋盘中心的两条(i==rowrow+i==n-1)、边放皇后边放镜像位(幻觉)、外层循环把起点概念搞混。从头重建。

4.1 骨架:每层一行,一次调用

N 皇后不需要起点森林 (单词搜索需要,这题不需要):单词搜索的路径可以从任何格子开始;N 皇后每行必须恰好放一个,处理顺序天然是第 0 行到第 n-1 行------一次调用 solve(0),第一行的皇后放哪列由第一层的 for 决定。

4.2 对角线的数学:这题的灵魂

皇后攻击线有三种(行不用查------每层只放一行,天然不冲突)。列很直观,两条对角线是灵魂。在对角线上走一步,坐标变化是固定的:

复制代码
↗ 方向走一步:r+1, c-1 → r+c 不变     → 同一条 ↗ 对角线上,r+c 恒定
↘ 方向走一步:r+1, c+1 → r-c 不变     → 同一条 ↘ 对角线上,r-c 恒定

拿 4×4 棋盘把 r+c 算出来,相同数字就是同一条对角线:

复制代码
r+c:      r-c:
0 1 2 3      0 -1 -2 -3
1 2 3 4      1  0 -1 -2
2 3 4 5      2  1  0 -1
3 4 5 6      3  2  1  0
冲突线 标记数组 索引 大小
cols[col] col ∈ 0, n-1 n
↗ 对角线 diag1[r+c] r+c ∈ 0, 2n-2 2n-1
↘ 对角线 diag2[r-c+n-1] r-c ∈ -(n-1), n-1加 n-1 偏移消灭负数 2n-1

全棋盘共 4n-2 条对角线,r±c每一条唯一编号------这就是"核心是对斜线进行编号"。

4.3 三个标记数组 + 四动作对

go 复制代码
func solveNQueens(n int) [][]string {
	res := [][]string{}
	pan := make([][]byte, n)
	for i := range pan {
		pan[i] = make([]byte, n)
		for j := range pan[i] {
			pan[i][j] = '.'
		}
	}
	cols := make([]bool, n)      // 列占用
	diag1 := make([]bool, 2*n-1) // ↗ 对角线占用:编号 r+c
	diag2 := make([]bool, 2*n-1) // ↘ 对角线占用:编号 r-c+n-1

	var solve func(row int)
	solve = func(row int) {
		if row == n {                       // 判终:收集棋盘
			sol := make([]string, n)
			for i := range pan {
				sol[i] = string(pan[i])
			}
			res = append(res, sol)
			return
		}
		for col := 0; col < n; col++ {      // 决策:这行放哪列
			if cols[col] || diag1[row+col] || diag2[row-col+n-1] {
				continue                     // 任何一条线被占,换列
			}
			cols[col] = true                // 做选择:三个标记 + 棋盘
			diag1[row+col] = true
			diag2[row-col+n-1] = true
			pan[row][col] = 'Q'
			solve(row + 1)                  // 下沉:下一行
			cols[col] = false               // 还原:四个动作逐一撤销
			diag1[row+col] = false
			diag2[row-col+n-1] = false
			pan[row][col] = '.'
		}
	}
	solve(0)
	return res
}

重写版用解数序列验证到 n=8:1 0 0 2 10 4 40 92,全对。注意"做选择"和"还原"是四个动作对(三标记 + 棋盘字符)------做多少,撤销多少,纪律和双动作时代一样。

4.4 "折叠判断"的第四次登场

回头看一条暗线:把 O(n) 的检查折叠成 O(1) 查表 ,这个招式出现了四次------131 的回文表(回文判断 O(n) → 查表)、140 的 can 数组(可行性折叠)、464 的位掩码(集合判断折叠)、51 的对角线标记数组(冲突检查 O(n) → 查表)。每次载体不同,原理同一:约束的判断与路径无关,就可以预计算/增量维护,变成查表

5. 识别框架:三层漏斗

十一类题翻下来,最后把"拿到题怎么快速判断"沉淀成三层漏斗,每层 10 秒:

5.1 第一层:范式闸门(是不是回溯)

题面问法 范式
"返回所有方案 / 列举全部" 回溯
"能否... / 存在一种..." 回溯(短路版)或 DP
"最少/最大/最多" DP 或贪心
"多少方法" DP
n ≤ 20 枚举可接受,回溯候选
n ≥ 10^4 与回溯无缘

5.2 第二层:家族闸门(用哪个骨架)

  • "顺序有意义吗?" 有 → 排列(used + 全范围循环);无 → 组合(start 推进)
  • "切字符串/序列吗?" → 切割(切多长)
  • "每个元素可以不选吗?" → 要/不要(子集)

5.3 第三层:延伸模块扫描(加什么装备)

题面信号 挂载模块
"候选可能含重复" / "解不能重复" 同层去重(排序相邻 or 层内集合)
"判断能否" / "返回任意一个" 存在性短路
"最少切几刀" / "最多拆几段" 排序 + break 剪枝;最优值优先想 DP
段的性质被反复判断(回文/递增/求和) 预处理表(O(n) 判断折叠成 O(1) 查表)
约束只看计数不看排列 计数器状态(如括号的 open/close)
"划分成 k 组" 分组骨架(元素 → 桶)
状态与路径分离(切分/网格/博弈) 记忆化(缓存结论

其中最后一行的判断法:先问"我的状态是什么",再问"不同路径会坍缩到同一状态吗"------会,就必须想记忆化;排列/组合/子集的路径即状态,永远不用想。

5.4 实操流程:迭代式

复制代码
读题 → 三层漏斗过一遍(30 秒)
     → 写朴素骨架(家族模板 + 命中的延伸模块)
     → 拿极端用例试跑(无解、全重复、最大规模)
        ├─ 超时 → 补记忆化(问自己:缓存的是结论还是选择?)
        ├─ 结果重复 → 补同层去重(能用排序吗?)
        └─ 缺解 → 决策粒度或模型错了,回炉四检验
     → 写码前扫终极检查清单

判断延伸不必一次全对------极端用例会替你暴露缺什么。

6. 终极检查清单

写任何回溯题之前,按这张表过一遍:

  • 范式:要"所有方案"才用回溯;只要"能不能/多少种"去想 DP
  • 决策:看着答案长出来,每层一个原子动作;位置能空 → 要/不要,不能空 → 选一个/切一段
  • 四检验:原子性(移动+匹配不可拆)?中间态合法(拆开的状态题目里存在吗)?覆盖完整?约束局部可判?
  • 判终 :"到达结尾"和"满足要求"是不是或
  • 收集:拷贝快照(slice 必须拷贝,string 不用);要所有方案就收集后继续,别短路
  • 还原:做选择/递归/还原三连,一个不能少;做了多少动作就撤销多少(N 皇后是四动作对)
  • 去重:题面有"含重复元素"→ 排序 + 同层去重;不能排序 → 层内哈希集合
  • 剪枝 :合法性(这个选择非法)+ 可行性(子树必死)两层都要想;break 的资格是单调性(排序给的,或切割长度天然给的),不是排序本身
  • 记忆化 :状态与路径分离吗?分离 → 缓存结论(能否/产出/长度),不缓存选择

总结

下半场的学习轨迹:

复制代码
139 范式陷阱 + 417 万次调用的超时现场(记忆化判据立住)
   ↓
140 记忆化三次错位(缓存选择 → 缓存候选 → 缓存结论)
   ↓
491 排序被禁(去重第四载体:层内集合)
   ↓
79 中间态失控(1GB 栈溢出,粒度过碎的翻车)
   ↓
51 N 皇后(状态设计崩塌 → 对角线编号 → 三个标记数组)
   ↓
真正毕业

感触最深的三个:

模板是廉价的,决策设计才是回溯的灵魂。所有翻车里,没有一次是"循环三连写错了",全是栽在"每层决策是什么"这个上游问题上------粒度过粗(括号)、粒度过碎(网格)、模型错配(要不要滥用)、中间态失控(step 机制)。

状态设计是第二灵魂。下半场的三记重锤(记忆化、去重第四载体、中间态)全都指向同一件事:状态是什么、状态和路径什么关系、状态怎么表示。把这三问想清楚,一半的坑在写代码之前就能排掉。

同一个考点会在不同题里反复伏击你。去重四连漏、记忆化三连错------每次都是"看懂了题解"和"能对着别人讲明白"之间的差距。错题不能只是看懂,要能推演(8 条路径表)、要能量化(417 万次调用)、要能重写(每一节的修正版我都重新写了一遍)。

说实话,这个过程比想象中难受,每个新题型都有新错法。但也真实感受到了收敛:从括号题整题建模错误,到 N 皇后崩塌后一次重写就过解数序列验证。算法能力就是这么一点点磨出来的,没有银弹。以后还需要继续努力。加油!

最后留一个接口:本篇第一章"记忆化的三次错位"里,"缓存结论、状态与路径分离、多条路径坍缩到同一状态"------这三句话正是动态规划的入场券。回溯加记忆化再去掉递归,就是 DP。接下来的动态规划系列,就从这里出发。

参考资料


版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。

相关推荐
纪卓志George1 小时前
打破语言范式:在 Go 里用动态代理实现 AOP
架构·go
HugoStudio_SWAN3 小时前
洛谷 P1420 / P1179 / B4262 最长连号、数字统计与词频统计——统计的三种面孔
c++·学习·程序人生·算法
青 春 记 忆3 小时前
LeetCode 350. 两个数组的交集 II|Python 解法详解
python·算法·leetcode
阳明山水4 小时前
销量预测2025—2026:从模型竞赛到系统融合的范式转型
人工智能·深度学习·算法·机器学习·架构
桐盛科技5 小时前
拒绝“漂绿”风险:基于边缘计算的碳排放数据清洗算法与实时校验逻辑
人工智能·算法·边缘计算
Tongzhi20265 小时前
通芝科技考勤软件三大发展趋势:AI大模型、无感识别与数据底座
大数据·科技·算法·爬山算法
eBest数字化转型方案5 小时前
Route Optimization for FMCG:多目标排线算法的工程实现
大数据·人工智能·算法
residual_fan6 小时前
持续对比强化学习(Continual Contrastive Reinforcement Learning)论文分享
人工智能·算法·数据挖掘·数据分析
s_w.h6 小时前
【 计网 】序列化与反序列化
linux·服务器·网络·算法·bash