多彩编程 多彩编程MZPH · CODE BLOG
ARTICLE DETAIL

文章详情

深耕前端与后端开发技术的一线实战笔记与踩坑复盘。

codeforces-go 周赛题解实战:LeetCode 2502 设计内存分配器(模拟 / Lazy 线段树 + 线段树二分)

codeforces-go 周赛题解实战:LeetCode 2502 设计内存分配器(模拟 / Lazy 线段树 + 线段树二分) 科学计算【免费下载链接】codeforces-go算法竞赛模板库 by 灵茶山艾府 项目地址https://gitcode.com/GitHub_Trending/co/codeforces-go点击查看免费下载导读本文基于 codeforces-go 仓库中 leetcode/weekly/323/c/README.md 的题解笔记完整讲解 LeetCode 第 323 场周赛 C 题「设计内存分配器」Design Memory Allocator, LeetCode 2502的两种解法O(n)线性模拟与O(log n)的 Lazy 线段树 线段树二分优化。读完本文你将掌握「在线性数组中查找最左连续空闲区间」「用线段树维护最长连续 0」「线段树二分找最左可行左端点」三大核心技术并能在本仓库的测试框架下直接运行验证。一、题目本质一个可复用的内存分配器类题目要求设计一个Allocator类支持以下操作n为内存单元总数方法语义Allocator(int n)初始化内存共n个单元初始全部空闲allocate(int size, int mID)找最小下标i使区间[i, isize-1]全部空闲将其赋值为mID并返回i找不到返回-1freeMemory(int mID)把所有值等于mID的单元清零释放返回释放的单元个数题目保证mID 0因此「内存单元值为 0」与「空闲」完全等价——这是两个解法共同依赖的建模基础把空闲记作 0、已分配记作正数分配即写入mID释放即写回 0。同一个mID可以对应多个互不相邻的区间多次allocate会为同一mID累积多个块freeMemory需要一次性释放该mID的全部块。仓库 c.txt 给出了标准样例n10时依次执行allocate(1,1)、allocate(1,2)、allocate(1,3)、free(2)、allocate(3,4)、allocate(1,1)、allocate(1,1)、free(1)、allocate(10,2)、free(7)期望输出依次为0, 1, 2, 1, 3, 1, 6, 3, -1, 0。其中allocate(10,2)返回-1因为剩余空闲单元不足 10 个free(7)返回0因为mID7从未分配过。这个样例贯穿全文可用来逐行验证两种解法的行为。二、方法一线性模拟O(qn)2.1 核心思路维护一个大小为n的数组a初始全为 0allocate(size, mID)从左到右遍历a用一个计数器free统计当前连续空闲值为 0的单元个数。遇到非 0 单元说明连续空闲被打断将free重置为 0否则free加一。当遍历到下标i时若free size说明以i-size1为左端点的连续size个单元全部空闲把该区间整体赋值为mID并返回左端点i-size1。遍历完仍未命中则返回-1。freeMemory(mID)遍历a把所有等于mID的元素置为 0同时统计个数作为答案返回。注意一个细节分配区间时要把[i-size1, i]整段写为mID而不是只写一个单元只有这样才能保证下一次遍历时已分配区间被正确识别为「非 0」。2.2 四语言参考实现以下实现直接取自 leetcode/weekly/323/c/README.md逻辑完全一致可相互对照学习class Allocator: def __init__(self, n: int): self.memory [0] * n def allocate(self, size: int, mID: int) - int: free 0 for i, id in enumerate(self.memory): if id 0: # 已分配 free 0 # 重新计数 continue free 1 if free size: # 找到了 self.memory[i - size 1: i 1] [mID] * size return i - size 1 return -1 # 无法分配内存 def freeMemory(self, mID: int) - int: ans 0 for i, id in enumerate(self.memory): if id mID: ans 1 self.memory[i] 0 # 标记为空闲内存 return ansclass Allocator { private final int[] memory; public Allocator(int n) { memory new int[n]; } public int allocate(int size, int mID) { int free 0; for (int i 0; i memory.length; i) { if (memory[i] 0) { // 已分配 free 0; // 重新计数 continue; } free; if (free size) { // 找到了 Arrays.fill(memory, i - size 1, i 1, mID); return i - size 1; } } return -1; // 无法分配内存 } public int freeMemory(int mID) { int ans 0; for (int i 0; i memory.length; i) { if (memory[i] mID) { ans; memory[i] 0; // 标记为空闲内存 } } return ans; } }class Allocator { vectorint memory; public: Allocator(int n) : memory(n) {} int allocate(int size, int mID) { int free 0; for (int i 0; i memory.size(); i) { if (memory[i] 0) { // 已分配 free 0; // 重新计数 continue; } free; if (free size) { // 找到了 fill(memory.begin() (i - size 1), memory.begin() (i 1), mID); return i - size 1; } } return -1; // 无法分配内存 } int freeMemory(int mID) { int ans 0; for (int i 0; i memory.size(); i) { if (memory[i] mID) { ans; memory[i] 0; // 标记为空闲内存 } } return ans; } };type Allocator []int func Constructor(n int) Allocator { return make([]int, n) } func (a Allocator) Allocate(size, mID int) int { free : 0 for i, id : range a { if id 0 { // 已分配 free 0 // 重新计数 continue } free if free size { // 找到了 for j : i - size 1; j i; j { a[j] mID } return i - size 1 } } return -1 // 无法分配内存 } func (a Allocator) FreeMemory(mID int) (ans int) { for i, id : range a { if id mID { ans a[i] 0 // 标记为空闲内存 } } return }2.3 复杂度分析时间复杂度allocate与freeMemory均为O(n)设q为两者调用次数之和总体为O(qn)。空间复杂度O(n)。该写法正确性直观、编码量极小适合作为对拍基准与面试快速实现。但当n很大、操作频繁时线性扫描会成为瓶颈于是引出下面的线段树优化。三、方法二Lazy 线段树 线段树二分选读O(n q log n)3.1 问题到数据结构的映射把空闲单元记作 0、已分配记作 1allocate等价于找最左边的区间左端点最小满足区间全为 0 且长度 sizefreeMemory等价于把mID对应的所有区间一次性置为 0。用线段树维护区间内的最长连续 0 的个数max0就能快速判断某个区间「能否容纳 size 个连续空闲单元」。前置知识Lazy 线段树区间赋值 懒标记下传、线段树二分。前置题目LeetCode 2213「由单个字符重复的最长子字符串」思想与本题目类似同样是维护前缀/后缀/最大值三段信息并在树上二分。3.2 如何维护max0初始时所有单元空闲任意区间的max0都等于区间长度。当前节点的max0等于三者最大值左儿子的max0右儿子的max0左儿子后缀连续 0 的个数 右儿子前缀连续 0 的个数即横跨左右儿子拼接而成的连续 0 段。因此除了max0每个节点还需额外维护pre0区间前缀连续 0 的个数suf0区间后缀连续 0 的个数。合并pushup逻辑pre0先取左儿子的pre0若左儿子整个区间全为 0即pre0 左区间长度则再加上右儿子的pre0suf0对称处理max0 max(左.max0, 右.max0, 左.suf0 右.pre0)。3.3 如何用线段树二分计算 allocate从根节点递归优先递归左子树若左子树中存在满足「全 0 且长度 size」的区间直接返回其左端点否则检查「左子树的suf0 右子树的pre0是否 size」若满足说明有一个跨中点的可行区间其左端点为mid - 左子树.suf0 1否则递归右子树。递归剪枝边界条件若当前节点max0 size该区间内必然不存在可行区间直接返回-1若当前区间长度为 1此时必有size 1返回该区间左端点。3.4 用哈希表记录 mID 的区间仅靠线段树无法知道某个mID对应哪些区间因此额外维护哈希表blockskey 为mIDvalue 为区间列表allocate找到左端点i后把区间[i, isize-1]加入blocks[mID]并调用线段树update将该区间整体置为 1freeMemory遍历blocks[mID]中所有区间累加各区间长度作为答案并逐一用update置为 0最后从哈希表删除该mID。3.5 四语言参考实现class Node: __slots__ pre0, suf0, max0, todo class SegTree: def __init__(self, n: int) - None: self.n n self.t [Node() for _ in range(2 (n - 1).bit_length())] self.build(1, 0, n - 1) def do(self, i: int, l: int, r: int, v: int) - None: size 0 if v 0 else r - l 1 self.t[i].pre0 size self.t[i].suf0 size self.t[i].max0 size self.t[i].todo v # 下传懒标记 def spread(self, o: int, l: int, r: int) - None: v self.t[o].todo if v ! -1: m (l r) // 2 self.do(o * 2, l, m, v) self.do(o * 2 1, m 1, r, v) self.t[o].todo -1 # 初始化线段树 def build(self, o: int, l: int, r: int) - None: self.do(o, l, r, -1) if l r: return m (l r) // 2 self.build(o * 2, l, m) self.build(o * 2 1, m 1, r) # 把区间 [ql, qr] 都置为 v def update(self, o: int, l: int, r: int, ql: int, qr: int, v: int) - None: if ql l and r qr: self.do(o, l, r, v) return self.spread(o, l, r) m (l r) // 2 if ql m: self.update(o * 2, l, m, ql, qr, v) if m qr: self.update(o * 2 1, m 1, r, ql, qr, v) # 合并左右子树的信息 lo self.t[o * 2] ro self.t[o * 2 1] # 区间前缀连续 0 的个数 self.t[o].pre0 lo.pre0 if lo.pre0 m - l 1: self.t[o].pre0 ro.pre0 # 和右子树的 pre0 拼起来 # 区间后缀连续 0 的个数 self.t[o].suf0 ro.suf0 if ro.suf0 r - m: self.t[o].suf0 lo.suf0 # 和左子树的 suf0 拼起来 # 区间最长连续 0 的个数 self.t[o].max0 max(lo.max0, ro.max0, lo.suf0 ro.pre0) # 线段树二分找最左边的区间左端点满足区间全为 0 且长度 size # 如果不存在这样的区间返回 -1 def find_first(self, o: int, l: int, r: int, size: int) - int: if self.t[o].max0 size: return -1 if l r: return l self.spread(o, l, r) m (l r) // 2 idx self.find_first(o * 2, l, m, size) # 递归左子树 if idx 0: # 左子树的后缀 0 个数 右子树的前缀 0 个数 size if self.t[o * 2].suf0 self.t[o * 2 1].pre0 size: return m - self.t[o * 2].suf0 1 idx self.find_first(o * 2 1, m 1, r, size) # 递归右子树 return idx class Allocator: def __init__(self, n: int): self.n n self.tree SegTree(n) self.blocks defaultdict(list) def allocate(self, size: int, mID: int) - int: i self.tree.find_first(1, 0, self.n - 1, size) if i 0: # 无法分配内存 return -1 # 分配内存 [i, isize-1] self.blocks[mID].append((i, i size - 1)) self.tree.update(1, 0, self.n - 1, i, i size - 1, 1) return i def freeMemory(self, mID: int) - int: ans 0 for l, r in self.blocks[mID]: ans r - l 1 self.tree.update(1, 0, self.n - 1, l, r, 0) # 释放内存 del self.blocks[mID] return ansclass SegTree { private final int[] pre0; // 区间前缀连续 0 的个数 private final int[] suf0; // 区间后缀连续 0 的个数 private final int[] max0; // 区间最长连续 0 的个数 private final int[] todo; // 懒标记 public SegTree(int n) { int size 2 (32 - Integer.numberOfLeadingZeros(n - 1)); pre0 new int[size]; suf0 new int[size]; max0 new int[size]; todo new int[size]; build(1, 0, n - 1); } // 把 [ql, qr] 都置为 v public void update(int o, int l, int r, int ql, int qr, int v) { if (ql l r qr) { do_(o, l, r, v); return; } spread(o, l, r); int m (l r) / 2; int lo o * 2; int ro lo 1; if (ql m) { update(lo, l, m, ql, qr, v); } if (m qr) { update(ro, m 1, r, ql, qr, v); } // 合并左右子树的信息 pre0[o] pre0[lo]; if (pre0[lo] m - l 1) { pre0[o] pre0[ro]; // 和右子树的 pre0 拼起来 } suf0[o] suf0[ro]; if (suf0[ro] r - m) { suf0[o] suf0[lo]; // 和左子树的 suf0 拼起来 } max0[o] Math.max(Math.max(max0[lo], max0[ro]), suf0[lo] pre0[ro]); } // 线段树二分找最左边的区间左端点满足区间全为 0 且长度 size // 如果不存在这样的区间返回 -1 public int findFirst(int o, int l, int r, int size) { if (max0[o] size) { return -1; } if (l r) { return l; } spread(o, l, r); int m (l r) / 2; int lo o * 2; int ro lo 1; int idx findFirst(lo, l, m, size); // 递归左子树 if (idx 0) { // 左子树的后缀 0 个数 右子树的前缀 0 个数 size if (suf0[lo] pre0[ro] size) { return m - suf0[lo] 1; } idx findFirst(ro, m 1, r, size); // 递归右子树 } return idx; } // 初始化线段树 private void build(int o, int l, int r) { do_(o, l, r, -1); if (l r) { return; } int m (l r) / 2; build(o * 2, l, m); build(o * 2 1, m 1, r); } private void do_(int i, int l, int r, int v) { int size v 0 ? r - l 1 : 0; pre0[i] suf0[i] max0[i] size; todo[i] v; } // 下传懒标记 private void spread(int o, int l, int r) { int v todo[o]; if (v ! -1) { int m (l r) / 2; do_(o * 2, l, m, v); do_(o * 2 1, m 1, r, v); todo[o] -1; } } } class Allocator { private final int n; private final SegTree tree; private final MapInteger, Listint[] blocks new HashMap(); public Allocator(int n) { this.n n; this.tree new SegTree(n); } public int allocate(int size, int mID) { int i tree.findFirst(1, 0, n - 1, size); if (i 0) { // 无法分配内存 return -1; } // 分配内存 [i, isize-1] blocks.computeIfAbsent(mID, k - new ArrayList()).add(new int[]{i, i size - 1}); tree.update(1, 0, n - 1, i, i size - 1, 1); return i; } public int freeMemory(int mID) { int ans 0; Listint[] list blocks.get(mID); if (list ! null) { for (int[] range : list) { ans range[1] - range[0] 1; tree.update(1, 0, n - 1, range[0], range[1], 0); // 释放内存 } blocks.remove(mID); } return ans; } }class SegTree { struct Node { int pre0, suf0, max0, todo; }; vectorNode t; void do_(int i, int l, int r, int v) { auto o t[i]; int size v 0 ? r - l 1 : 0; o.pre0 o.suf0 o.max0 size; o.todo v; } // 下传懒标记 void spread(int o, int l, int r) { int v t[o].todo; if (v ! -1) { int m (l r) / 2; do_(o * 2, l, m, v); do_(o * 2 1, m 1, r, v); v -1; } } // 初始化线段树 void build(int o, int l, int r) { do_(o, l, r, -1); if (l r) { return; } int m (l r) / 2; build(o * 2, l, m); build(o * 2 1, m 1, r); } public: SegTree(int n) { t.resize(2 bit_width((unsigned) n - 1)); build(1, 0, n - 1); } // 把 [ql, qr] 都置为 v void update(int o, int l, int r, int ql, int qr, int v) { if (ql l r qr) { do_(o, l, r, v); return; } spread(o, l, r); int m (l r) / 2; if (ql m) { update(o * 2, l, m, ql, qr, v); } if (m qr) { update(o * 2 1, m 1, r, ql, qr, v); } // 合并左右子树的信息 Node lo t[o * 2]; Node ro t[o * 2 1]; // 区间前缀连续 0 的个数 t[o].pre0 lo.pre0; if (lo.pre0 m - l 1) { t[o].pre0 ro.pre0; // 和右子树的 pre0 拼起来 } // 区间后缀连续 0 的个数 t[o].suf0 ro.suf0; if (ro.suf0 r - m) { t[o].suf0 lo.suf0; // 和左子树的 suf0 拼起来 } // 区间最长连续 0 的个数 t[o].max0 max({lo.max0, ro.max0, lo.suf0 ro.pre0}); } // 线段树二分找最左边的区间左端点满足区间全为 0 且长度 size // 如果不存在这样的区间返回 -1 int find_first(int o, int l, int r, int size) { if (t[o].max0 size) { return -1; } if (l r) { return l; } spread(o, l, r); int m (l r) / 2; int idx find_first(o * 2, l, m, size); // 递归左子树 if (idx 0) { // 左子树的后缀 0 个数 右子树的前缀 0 个数 size if (t[o * 2].suf0 t[o * 2 1].pre0 size) { return m - t[o * 2].suf0 1; } idx find_first(o * 2 1, m 1, r, size); // 递归右子树 } return idx; } }; class Allocator { int n; SegTree tree; unordered_mapint, vectorpairint, int blocks; public: Allocator(int n) : n(n), tree(n) {} int allocate(int size, int mID) { int i tree.find_first(1, 0, n - 1, size); if (i 0) { // 无法分配内存 return -1; } blocks[mID].emplace_back(i, i size - 1); tree.update(1, 0, n - 1, i, i size - 1, 1); // 分配内存 [i, isize-1] return i; } int freeMemory(int mID) { int ans 0; for (auto [l, r] : blocks[mID]) { ans r - l 1; tree.update(1, 0, n - 1, l, r, 0); // 释放内存 } blocks.erase(mID); return ans; } };type segTree []struct { l, r, pre0, suf0, max0, todo int } func newSegTree(n int) segTree { t : make(segTree, 2bits.Len(uint(n-1))) t.build(1, 0, n-1) return t } func (t segTree) do(i, v int) { o : t[i] size : 0 if v 0 { size o.r - o.l 1 } o.pre0 size o.suf0 size o.max0 size o.todo v } // 下传懒标记 func (t segTree) spread(o int) { v : t[o].todo if v ! -1 { t.do(o1, v) t.do(o1|1, v) t[o].todo -1 } } // 初始化线段树 func (t segTree) build(o, l, r int) { t[o].l, t[o].r l, r t.do(o, -1) if l r { return } m : (l r) 1 t.build(o1, l, m) t.build(o1|1, m1, r) } // 把 [l, r] 都置为 v func (t segTree) update(o, l, r, v int) { if l t[o].l t[o].r r { t.do(o, v) return } t.spread(o) m : (t[o].l t[o].r) 1 if l m { t.update(o1, l, r, v) } if m r { t.update(o1|1, l, r, v) } // 合并左右子树的信息 lo, ro : t[o1], t[o1|1] // 区间前缀连续 0 的个数 t[o].pre0 lo.pre0 if lo.pre0 m-t[o].l1 { t[o].pre0 ro.pre0 // 和右子树的 pre0 拼起来 } // 区间后缀连续 0 的个数 t[o].suf0 ro.suf0 if ro.suf0 t[o].r-m { t[o].suf0 lo.suf0 // 和左子树的 suf0 拼起来 } // 区间最长连续 0 的个数 t[o].max0 max(lo.max0, ro.max0, lo.suf0ro.pre0) } // 线段树二分找最左边的区间左端点满足区间全为 0 且长度 size // 如果不存在这样的区间返回 -1 func (t segTree) findFirst(o, size int) int { if t[o].max0 size { return -1 } if t[o].l t[o].r { return t[o].l } t.spread(o) idx : t.findFirst(o1, size) // 递归左子树 if idx 0 { // 左子树的后缀 0 个数 右子树的前缀 0 个数 size if t[o1].suf0t[o1|1].pre0 size { m : (t[o].l t[o].r) 1 return m - t[o1].suf0 1 } idx t.findFirst(o1|1, size) // 递归右子树 } return idx } // 上面为线段树代码 type interval struct { l, r int } type Allocator struct { tree segTree blocks map[int][]interval } func Constructor(n int) Allocator { return Allocator{ tree: newSegTree(n), blocks: map[int][]interval{}, } } func (a Allocator) Allocate(size, mID int) int { i : a.tree.findFirst(1, size) if i 0 { // 无法分配内存 return -1 } a.blocks[mID] append(a.blocks[mID], interval{i, i size - 1}) a.tree.update(1, i, isize-1, 1) // 分配内存 [i, isize-1] return i } func (a Allocator) FreeMemory(mID int) (ans int) { for _, p : range a.blocks[mID] { ans p.r - p.l 1 a.tree.update(1, p.l, p.r, 0) // 释放内存 } delete(a.blocks, mID) return }3.6 复杂度分析时间复杂度初始化建树O(n)allocate为O(log n)线段树二分 一次区间赋值freeMemory均摊O(log n)因为释放内存单元的总次数不会超过allocate分配的次数哈希表中区间总量也受allocate调用次数约束。总体O(n q log n)其中q为allocate与freeMemory调用次数之和。空间复杂度O(n)。注意哈希表blocks中区间总长度不会超过n因此整体仍为O(n)。四、仓库内的源码实现与测试验证4.1 核心实现Go 版README 中的 Go 线段树解法与仓库内提交版 c.go 完全一致。仓库实现有两点值得注意节点结构segTree是一个[]struct每个节点保存l, r, pre0, suf0, max0, todo六个字段其中l, r记录节点覆盖区间Go 版通过参数化递归避免了在递归函数里反复传区间参数todo为懒标记用-1表示「无待下传标记」数组大小make(segTree, 2bits.Len(uint(n-1)))使用math/bits计算不小于n-1的最小 2 的幂再左移一位保证 4 倍空间满足任意递归节点索引不越界——这也是 Go 线段树模板的常见安全写法其余语言对应2 bit_length(n-1)/2 bit_width(n-1)/2 (32 - numberOfLeadingZeros(n-1))。4.2 自动化测试与数据驱动验证仓库为本题提供了完整的测试闭环可以无脑验证两种解法行为是否一致测试入口 c_test.go 通过testutil.RunLeetCodeClassWithFile(t, Constructor, c.txt, targetCaseNum)驱动测试targetCaseNum : 0表示跑全部用例并启用超时检测超时即判 TLE测试数据 c.txt 采用「每三行一组」的类测试格式第一行是操作序列、第二行是参数、第三行是期望输出正是本文开头介绍的标准样例底层框架位于 leetcode/testutil/leetcode.goRunLeetCodeClassWithFile读取文件后按每 3 行切分为用例组再交给RunLeetCodeClassWithExamples用反射按调用序列逐条执行并做字符串级输出比对。顺带一提这类周赛题目录与测试样例在本仓库内由 copypasta/template/leetcode/generator_test.go 的TestWeekly机制批量生成自动拉取下一场周赛并产出目录与测试数据这正是本仓库「题解 模板 自动化验证」一体化的体现。五、两种方案选型对比维度方法一线性模拟方法二Lazy 线段树 线段树二分allocateO(n)O(log n)freeMemoryO(n)均摊O(log n)总体O(qn)O(n q log n)空间O(n)O(n)编码难度低直观易写较高需掌握三段信息合并与树上二分适用场景n与q较小时快速过题大数据量、频繁操作时的进阶优化两个方案在本题的数据范围下均可 AC方法二的价值在于把「最左可行区间查找」这一模式从线性扫描升级为对数查询其pre0/suf0/max0三段式维护思路可迁移到「最长连续子段」「区间覆盖 查询最长全 0 段」等一系列线段树问题如前置题 LeetCode 2213。六、相关分类题单本题属于「常用数据结构线段树」与「区间/数据结构优化」类问题。若要围绕本题的训练主线进行系统刷题可按以下方向展开题单分类来自 leetcode/weekly/323/c/README.md 的整理滑动窗口与双指针定长/不定长/单序列/双序列/三指针/分组循环二分算法二分答案/最小化最大值/最大化最小值/第 K 小单调栈基础/矩形面积/贡献法/最小字典序网格图DFS/BFS/综合应用位运算基础/性质/拆位/试填/恒等式/思维图论算法DFS/BFS/拓扑排序/最短路/最小生成树/二分图/基环树/欧拉路径动态规划入门/背包/状态机/划分/区间/状压/数位/数据结构优化/树形/博弈/概率期望常用数据结构前缀和/差分/栈/队列/堆/字典树/并查集/树状数组/线段树数学算法数论/组合/概率期望/博弈/计算几何/随机算法贪心与思维基本贪心策略/反悔/区间/字典序/数学/思维/脑筋急转弯/构造链表、二叉树与回溯前后指针/快慢指针/DFS/BFS/直径/LCA/一般树字符串KMP/Z 函数/Manacher/字符串哈希/AC 自动机/后缀数组/子序列自动机。其中「常用数据结构」与「滑动窗口与双指针」两个分类与本题的「连续区间查找」主题最为贴近建议优先攻克。本文对应题解与四语言代码均保存在 leetcode/weekly/323/c/README.md可随时对照复习。赞分享科学计算【免费下载链接】codeforces-go算法竞赛模板库 by 灵茶山艾府 项目地址https://gitcode.com/GitHub_Trending/co/codeforces-go点击查看免费下载相关推荐codeforces-go 仓库解读力扣双周赛 150 四题精讲遍历求和 / 浮点与整数二分 / 差分扫描线 / Lazy 线段树 / KMP 三指针codeforces go 仓库解读力扣双周赛 150 四题精讲遍历求和 / 浮点与整数二分 / 差分扫描线 / Lazy 线段树 / KMP 三指针 本科学计算力扣双周赛 150「分割正方形 II」题解Lazy 线段树 扫描线求面积平分线力扣双周赛 150「分割正方形 II」题解Lazy 线段树 扫描线求面积平分线 本文以力扣双周赛 150 第三题 Separate Squares II科学计算codeforces-go 题解精讲划分型动态规划与线段树优化解 LeetCode 周赛 329 分割数组最小代价codeforces go 题解精讲划分型动态规划与线段树优化解 LeetCode 周赛 329 分割数组最小代价 导读 本文以 LeetCode 第 329科学计算上一篇打破语言壁垒5个步骤让Translumo成为你的屏幕翻译利器下一篇5分钟搞定B站视频下载免费解锁大会员4K高清画质创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表