写在前面

欢迎大家关注Rocky的知乎:Rocky Ding
《三年面试五年模拟》AIGC/LLM/AI Agent算法工程师/开发工程师求职面试秘籍独家资源:【三年面试五年模拟】WeThinkIn/AIGC-Interview-Book,欢迎大家Star~
Rocky最新撰写的10万字AI Agent(AI智能体)深入浅出全维度解析文章: 深入浅出完整解析AI Agent(AI智能体)的核心基础知识
AIGC/LLM/AI Agent算法岗/开发岗求职面试内推学习社群 (涵盖AIGC、LLM大模型、AI Agent、传统深度学习、自动驾驶、机器学习、计算机视觉、自然语言处理、强化学习、大数据挖掘、具身智能、元宇宙、AGI等AI行业最新面试干货经验与核心知识)欢迎大家加入:https://t.zsxq.com/33pJ0
大家好,我是Rocky。
编程题
1. 编程题:求abc方案数
题意:
给定数字 n,要求统计有多少组 (a, b, c) 满足:
1 ≤ a ≤ b ≤ c ≤ n 1 \le a \le b \le c \le n 1≤a≤b≤c≤n
并且:
a ⊕ b ⊕ c = 0 , a + b > c a \oplus b \oplus c = 0,\qquad a+b>c a⊕b⊕c=0,a+b>c
其中 ^ 表示按位异或。
题解:
异或满足结合律,并且 x ⊕ x = 0 x \oplus x=0 x⊕x=0、 x ⊕ 0 = x x \oplus 0=x x⊕0=x。由
a ⊕ b ⊕ c = 0 a \oplus b \oplus c=0 a⊕b⊕c=0
可得:
c = a ⊕ b c=a \oplus b c=a⊕b
因此不需要枚举三元组,只需枚举 a 和 b,计算唯一可能的 c,然后检查大小关系和 a+b>c。这已经把朴素的 O(n^3) 降为 O(n^2)。
python
def count_abc(n: int) -> int:
ans = 0
for a in range(1, n + 1):
for b in range(a, n + 1):
c = a ^ b
if b <= c <= n and a + b > c:
ans += 1
return ans
这里必须保留 b <= c 检查,因为 c=a^b 并不保证三元组的非降序关系。a+b>c 也不能由异或条件推出,必须单独判断。
正确性检查: 对任意合法三元组,前两个数 (a,b) 一定会被枚举;异或等式又唯一确定 c=a^b,所以不会漏解,也不会因为同一个 (a,b) 产生多个 c。反过来,代码只统计同时满足 b <= c <= n 和 a+b>c 的候选,因此每个计数项都满足题面约束。
时间复杂度为 O(n^2),额外空间复杂度为 O(1)。n=1 或没有满足条件的组合时答案自然为 0;Python 整数不会溢出,但其他语言需要使用足够宽的计数类型。如果题目给出的 n 上限是约 2500,枚举 a,b 通常已经足够;是否需要更复杂的位运算优化,必须由真实数据范围和时间限制决定,不能仅凭"异或题"就过度优化。
2. 编程题:栈/队列判断
题意:
给定两个相同长度的字符串 S 和 T。按照 S 的顺序依次输入一个数据结构,判断该数据结构的输出顺序可以是否为 T。输出结果可能是:栈、队列、两者都可能,或者两者都不可能。
题解:
队列是先进先出。所有字符都按照 S 的顺序进入队列,因此它唯一可能的输出就是 S 本身:
- 如果
S == T,队列一定可行; - 如果
S != T,队列一定不可行。
栈是后进先出,但每次入栈后可以选择暂不弹出,也可以在合适时机弹出。用一个栈模拟即可:遍历 S,每读入一个字符就压栈;只要栈顶等于 T[j],就不断弹栈并推进 j。最终 j == len(T),说明存在一种弹栈顺序得到 T。
python
def judge_structure(s: str, t: str) -> str:
if len(s) != len(t):
return "neither"
queue_ok = s == t
stack = []
j = 0
for ch in s:
stack.append(ch)
while stack and j < len(t) and stack[-1] == t[j]:
stack.pop()
j += 1
stack_ok = j == len(t)
if queue_ok and stack_ok:
return "both"
if stack_ok:
return "stack"
if queue_ok:
return "queue"
return "neither"
例如 S=abc、T=acb 时,队列不可行,但可以先压入 a 并弹出,再压入 b,c,最后按 c,b 弹出,所以结果是 stack。如果 S=T,栈可以每次入栈后立即弹出,因而结果应为 both,不能简单地把 S=T 判成 queue。
为什么贪心弹栈是安全的: 当栈顶已经等于 T[j] 时,若不弹出,就无法让 T[j] 由栈中更底部的元素先输出;弹出它不会改变后续入栈顺序,且只会推进一个已经确定的目标字符。因此连续弹出所有匹配的栈顶字符不会丢失可行方案。若遍历结束后 j < len(T),说明剩余元素的栈顶顺序无法匹配目标,栈一定不可行。
空字符串(若题面允许)对栈和队列都可行;长度不同则两者都不可行。每个字符最多入栈和出栈一次,时间复杂度为 O(n),额外空间复杂度为 O(n)。实现时要特别注意重复字符,不能用字符到位置的单值映射代替栈模拟。
3. 编程题:任务最大收益
题意:
给定 n 个任务,每个任务有开始和结束时间 [s_i,e_i),以及价值 v_i。选择一组时间上互不冲突的任务,使价值总和最大。半开区间意味着任务 i 结束于 e_i 后,可以接着执行开始于 e_i 的任务。
题解:
这是带权区间调度问题。按结束时间升序排序,令 dp[i] 表示排序后前 i 个任务能够获得的最大价值。对于第 i 个任务,有两种选择:
- 不选它,收益为
dp[i-1]; - 选它,找到最后一个满足
e_j <= s_i的任务j,收益为dp[j] + v_i。
于是:
d p i = max ( d p i − 1 , d p p ( i ) + v i ) dpi=\max\bigl(dpi-1,\ dpp(i)+v_i\bigr) dpi=max(dpi−1, dpp(i)+vi)
其中 p(i) 是按结束时间排序后、结束时间不晚于 s_i 的最后一个任务下标。由于结束时间有序,p(i) 可以二分查找。
python
from bisect import bisect_right
from typing import List, Tuple
def max_task_value(tasks: List[Tuple[int, int, int]]) -> int:
# 每个任务是 (start, end, value),题目保证 start <= end。
tasks = sorted(tasks, key=lambda item: item[1])
ends = [end for _, end, _ in tasks]
dp = [0] * (len(tasks) + 1)
for i, (start, _, value) in enumerate(tasks, start=1):
# 在前 i-1 个任务中找最后一个 end <= start 的任务。
compatible = bisect_right(ends, start, 0, i - 1)
take = dp[compatible] + value
skip = dp[i - 1]
dp[i] = max(skip, take)
return dp[-1]
为什么可以只看最后一个兼容任务?因为前缀 dp[compatible] 已经代表所有结束时间不晚于 start_i 的任务组合的最优值,选择其中最后一个下标不会丢失更早任务的最优解。
排序耗时 O(n log n),每个任务进行一次二分查找,耗时 O(log n),总时间复杂度为 O(n log n),额外空间复杂度为 O(n)。如果使用按开始时间排序配合小根堆,也可以做到 O(n log n),但必须明确堆中维护的是已经完成任务的最优价值;仅仅"把任务放进堆"并不能自动保证动态规划转移正确。
正确性与边界: 排序后考虑前 i 个任务,最优解要么不含第 i 个任务(对应 dp[i-1]),要么包含它;后一种情况下,其余任务必须来自结束时间不晚于 s_i 的前缀,最优收益正是 dp[compatible]。两种情况覆盖且互斥,归纳即可得到转移正确。半开区间的兼容条件是 end <= start,所以必须使用 bisect_right。空任务列表返回 0;价值为负数时,dp[i-1] 会自然选择"不接这个任务",除非题目另有"必须选择至少一个任务"的约束。
4. 编程题:价值最大路径
题意:
给定 n 个点和 m 条单向边。每条边按输入顺序给出 (a_i,b_i,w_i),表示从 a_i 到 b_i、权重为 w_i 的边。需要选择一条边序列,使得:
- 后一条边的起点等于前一条边的终点;
- 后一条边的权重严格大于前一条边的权重;
- 后一条边必须在输入中晚于前一条边出现,但不要求边在输入数组中连续。
输出满足条件的最长路径边数。这里的"价值最大"容易造成误读:题目真正优化的是路径边数,w_i 只用于约束严格递增,不是让权重总和最大。
题解:
按输入顺序处理边。定义 best[v][w] 表示已经处理的边中,以点 v 为终点、最后一条边权为 w 的最长合法路径长度。
处理当前边 (a,b,w) 时,前一条边必须已经到达 a,且其权重严格小于 w,所以:
c u r = 1 + max u < w b e s t a u cur=1+\max_{u<w}bestau cur=1+u<wmaxbestau
然后把这条边作为新路径的最后一条边,更新 best[b][w] = max(best[b][w], cur)。查询必须先于更新 ,因为当前边不能接在自己后面;而且使用严格小于 w 的前缀查询,等权边不能连接。
每个点上的权重值域可以离散化,并为每个点维护一棵 Fenwick Tree(树状数组),树状数组支持:
- 查询权重排名小于当前权重的最大路径长度;
- 更新当前终点和当前权重对应的最大值。
python
from bisect import bisect_left
from collections import defaultdict
from typing import List, Tuple
class FenwickMax:
def __init__(self, size: int) -> None:
self.tree = [0] * (size + 1)
def update(self, index: int, value: int) -> None:
while index < len(self.tree):
self.tree[index] = max(self.tree[index], value)
index += index & -index
def query(self, index: int) -> int:
result = 0
while index > 0:
result = max(result, self.tree[index])
index -= index & -index
return result
def longest_increasing_path(n: int, edges: List[Tuple[int, int, int]]) -> int:
# 先按终点收集该点可能出现的所有权重,建立离散坐标。
weights = defaultdict(list)
for _, end, weight in edges:
weights[end].append(weight)
trees = {}
for vertex, values in weights.items():
ordered = sorted(set(values))
trees[vertex] = (ordered, FenwickMax(len(ordered)))
answer = 0
for start, end, weight in edges:
ordered, tree = trees[start] if start in trees else ([], None)
# 当前边的前驱必须权重严格小于 weight。
rank = bisect_left(ordered, weight)
previous = tree.query(rank) if tree is not None else 0
current = previous + 1
target_ordered, target_tree = trees[end]
target_rank = bisect_left(target_ordered, weight) + 1
target_tree.update(target_rank, current)
answer = max(answer, current)
return answer
上面代码中,树建立在"前一条边的终点"上;处理当前边时从 trees[start] 查询,再把结果更新到 trees[end]。这正是端点衔接条件的实现。
正确性检查: 按输入顺序处理到第 i 条边时,树中只保存第 i-1 条及之前的状态,因而自动满足"后一条边在输入中更晚"。查询 start 的权重前缀只取 < weight,恰好对应端点连续且权重严格递增的所有合法前驱;若没有前驱,前缀最大值为 0,当前边单独构成长度 1 的路径。再将 current 写入 end 的当前权重位置,就维护了所有以该状态结尾的最优值。对输入顺序归纳可知,最终 answer 是所有合法路径的最大边数。
参数 n 只描述点编号范围,算法实际只需读取边列表;空边列表会返回 0。权重可能相同或为负数,离散化和 bisect_left 仍然成立,关键是查询排名严格小于当前权重,而更新时使用当前权重的排名。设某个点关联的不同权重总数为 k_v,则每次查询和更新为 O(log k_v),总时间复杂度为 O(m log m),离散化和树状数组占用 O(m) 空间。若所有权重已经是小范围整数,也可以直接使用数组或线段树;不能把复杂度写成 O(m log n),除非题目明确说明权重值域或点数与权重离散规模存在相应约束。
推荐阅读
1. 深入浅出完整解析AI Agent(AI智能体)的核心基础知识
2025年可以说是AI Agent全面落地应用的元年,因此Rocky在持续撰写对AI Agent的全维度解析文章:
深入浅出完整解析AI Agent(AI智能体)的核心基础知识
2. 深入浅出完整解析扩散模型DDPM、DDIM、Score-Based、SDE、LDM、Classifier/Classifier-Free Guidance、Rectified Flow核心基础知识
Rocky对扩散模型的本质原理与和核心基础知识进行了全面系统的深入浅出分析讲解,同时不断跟进补充扩散模型的最新技术发展,希望能给大家带来帮助:
深入浅出完整解析扩散模型DDPM、DDIM、Score-Based、SDE、LDM、Classifier/Classifier-Free Guidance、Rectified Flow核心基础知识
3. 入浅出完整解析FLUX.2、Seedream(即梦)、Z-image、GLM-Image核心基础知识
Rocky对AIGC时代"中场时刻"之后的主流AIGC创作大模型的核心基础知识进行了全面系统的深入浅出分析讲解,力求让大家通俗易懂理解AIGC时代的技术浪潮的本质价值:
入浅出完整解析FLUX.2、Seedream(即梦)、Z-image、GLM-Image核心基础知识
4. 深入浅出完整解析FLUX.1 Kontext和FLUX.1 Krea核心基础知识
Rocky对FLUX.1 Kontext和FLUX.1 Krea的核心基础知识作了全面系统的梳理与解析:
深入浅出完整解析FLUX.1 Kontext和FLUX.1 Krea核心基础知识
5. 深入浅出完整解析DeepSeek系列核心基础知识
Rocky对DeepSeek系列模型的核心基础知识作了全面系统的梳理与解析:
6. 深入浅出完整解析Stable Diffusion 3(SD 3)和FLUX.1系列核心基础知识
Rocky对Stable Diffusion 3和FLUX.1的核心基础知识作了全面系统的梳理与解析:
深入浅出完整解析Stable Diffusion 3(SD 3)和FLUX.1系列核心基础知识
7. 深入浅出完整解析Stable Diffusion XL(SDXL)核心基础知识
Rocky对Stable Diffusion XL的核心基础知识作了全面系统的梳理与解析:
深入浅出完整解析Stable Diffusion XL(SDXL)核心基础知识
8. 深入浅出完整解析Stable Diffusion(SD)核心基础知识
Rocky对Stable Diffusion 1.x-2.x系列模型的核心基础知识做了全面系统的梳理与解析:
深入浅出完整解析Stable Diffusion(SD)核心基础知识
9. 深入浅出完整解析Stable Diffusion中U-Net的前世今生与核心知识
Rocky对Stable Diffusion中最为关键的U-Net结构进行了深入浅出的全面解析,包括其在传统深度学习中的价值和在AIGC中的价值:
深入浅出完整解析Stable Diffusion中U-Net的前世今生与核心知识
10. 深入浅出完整解析LoRA(Low-Rank Adaptation)模型核心基础知识
对于AIGC时代中的"ResNet"------LoRA模型,Rocky进行了深入浅出的全面讲解:
深入浅出完整解析LoRA(Low-Rank Adaptation)模型核心基础知识
11. 深入浅出完整解析ControlNet核心基础知识
AIGC图像创作开源社区已经形成以Stable Difffusion/FLUX为核心,ConrtolNet和LoRA作为首要AI辅助工具的变化万千的AIGC图像创作工作流。
ControlNet正是让AI图像创作社区无比繁荣的关键一环,它让AIGC图像创作过程更加的可控,更有助于广泛地将AIGC算法解决方案应用到各行各业中:
12. 深入浅出完整解析Sora、Seedance、keling等AI视频大模型核心基础知识
AI绘画和AI视频是两个互相促进、相互交融的领域,2024年无疑是AI视频领域的爆发之年,Rocky对AI视频领域核心的Sora、Seedance、Keling等大模型进行了全面系统的梳理与解析:
深入浅出完整解析Sora、Seedance、keling等AI视频大模型核心基础知识
13. 深入浅出完整解析AIGC时代Transformer核心基础知识
在AIGC时代中,Transformer为AI行业带来了深刻的变革。Transformer架构正在一步一步重构所有的AI技术方向,成为AI技术架构大一统与多模态整合的关键核心基座,大有一统"AI江湖"之势。Rocky也对Transformer模型进行持续的深入浅出梳理与解析:
深入浅出完整解析AIGC时代Transformer核心基础知识
14. 深入浅出完整解析ComfyUI、Diffusers、Stable Diffusion WebUI等主流AIGC创作框架核心基础知识
AIGC创作框架正是AIGC算法工作流的运行载体,目前主流的AIGC创作框架有ComfyUI、Diffusers、Stable Diffusion WebUI等 。在传统深度学习时代,PyTorch、TensorFlow以及Caffe是传统深度学习模型的基础运行框架,到了AIGC时代,Rocky相信ComfyUI就是AIGC时代的"PyTorch"、Stable Diffusion WebUI就是AIGC时代的"TensorFlow"、Diffusers就是AIGC时代的"Caffe":
深入浅出完整解析ComfyUI、Diffusers、Stable Diffusion WebUI等主流AIGC创作框架核心基础知识
15. 深入浅出完整解析ComfyUI、Diffusers、Stable Diffusion WebUI等主流AIGC创作框架核心基础知识
在AIGC时代中,如何快速转身,入局AIGC产业?如何成为AIGC/LLM/AI Agent算法/开发工程师?如何在学校中系统性学习AIGC/LLM/AI Agent知识,斩获心仪的AIGC/LLM/AI Agent算法/开发offer?
Don't worry,Rocky为大家总结整理了全面的AIGC/LLM/AI Agent算法/开发工程师成长秘籍,为大家答疑解惑,希望能给大家带来帮助:
手把手教你成为AIGC/LLM/AI Agent算法/开发工程师,斩获AIGC/LLM/AI Agent算法/开发offer!
16. AIGC产业的深度思考与分析
2023年3月21日,微软创始人比尔·盖茨在其博客文章《The Age of AI has begun》中表示,自从1980年首次看到图形用户界面(graphical user interface)以来,以OpenAI为代表的科技公司发布的AIGC模型是他所见过的最具革命性的技术进步。
Rocky也认为,AIGC及其生态,会成为AI行业重大变革的主导力量。AIGC会带来一个全新的红利期,未来随着AIGC的全面落地和深度商用,会深刻改变我们的工作、生活、学习以及交流方式,各行各业都将被重新定义,过程会非常有趣。
那么,在此基础上,我们该如何更好的审视AIGC的未来?我们该如何更好地拥抱AIGC引领的革新?Rocky准备从技术、产品、商业模式、长期主义等维度持续分享一些个人的核心思考与观点,希望能帮助各位读者对AIGC有一个全面的了解:
深入浅出全面解析AIGC时代核心价值与发展趋势(2025年版)
17. AI算法工程师的独孤九剑秘籍
为了方便大家实习、校招以及社招的面试准备,同时帮助大家提升扩展技术基本面,Rocky将符合大厂和AI独角兽价值的算法高频面试知识点撰写总结成《三年面试五年模拟》之独孤九剑秘籍:
【三年面试五年模拟】AIGC时代的算法工程师的求职面试秘籍(持续更新中)
18. 深入浅出完整解析AIGC时代中GAN(Generative Adversarial Network)系列模型核心基础知识
GAN系列模型作为传统深度学习时代的最热门生成式Al模型,在AIGC时代继续繁荣,作为Stable Diffusion/FLUX系列大模型的"得力助手",广泛活跃于AlGC图像创作的产品与工作流中:
深入浅出完整解析AIGC时代中GAN(Generative Adversarial Network)系列模型核心基础知识