leecodecode【回溯子集】【2026.6.4打卡-java版本】

电话号码的字母组合

要点:dfs,什么时候返回,这一步要干什么,下一步是什么,选或者不选?

传递i,path,s

java 复制代码
class Solution {
    private static final String[] MAPPING = {
        "",     // 0
        "",     // 1
        "abc",  // 2
        "def",  // 3
        "ghi",  // 4
        "jkl",  // 5
        "mno",  // 6
        "pqrs", // 7
        "tuv",  // 8
        "wxyz"  // 9
    };

    public List<String> letterCombinations(String digits) {
        //回溯
         int n = digits.length();
        List<String> ans = new ArrayList<>();
        char[] path = new char[n];

        dfs(0,path,ans, digits.toCharArray());

        return ans;
      
    }

    public void dfs(int i, char[] path, List<String> ans, char[] digits){
        if(i == digits.length ){
            ans.add(new String(path));
            return;
        }

        String temp = MAPPING[digits[i] - '0'];

        for(char c : temp.toCharArray()){
            path[i] = c;
            dfs(i+1,path,ans,digits);
        }
    }



    
}

子集

要点:

方法1: path,ans,什么时候ans。add(ans),dfs(i), 不选的dfs(i+1), 选择则加然后df还要回溯

java 复制代码
class Solution {
    private final List<List<Integer>> ans =new ArrayList<>();
    private final List<Integer> path = new ArrayList<>();
    private int[] nums;

    public List<List<Integer>> subsets(int[] nums) {
        this.nums = nums;
        dfs(0);
        return ans;
    }


    private void dfs(int i){

        if(i == nums.length){
            ans.add(new ArrayList<>(path));
            return;
        }

        //不选
        dfs(i+1);

        //选择
        path.add(nums[i]);
        dfs(i+1);
        path.remove(path.size() - 1);
        
    }
}

方法2:从答案的视角

c里面有for(j=i,开始循环,dfs)

java 复制代码
class Solution {
    private final List<List<Integer>> ans =new ArrayList<>();
    private final List<Integer> path = new ArrayList<>();
    private int[] nums;

    public List<List<Integer>> subsets(int[] nums) {
        //要恢复现场,之前怎么样现在就是怎么样
        this.nums = nums;
        dfs(0);
        return ans;
    }


    private void dfs(int i){
        ans.add(new ArrayList<>(path));
        if(i == nums.length){
            return;
        }
        for(int j = i; j < nums.length; j++){
        //选择
        path.add(nums[j]);
        dfs(j+1);
        path.remove(path.size() - 1);
        }

    }
}

分割回文串

要点:判断回文,选dfs(要判断现在是否回文)还是不选【dfs(i+1)】,传参是i,start

java 复制代码
class Solution {
    public List<List<String>> partition(String s) {
        //选不选的视角
        List<List<String>> ans = new ArrayList<>();
        List<String> path = new ArrayList<>();

        dfs(0, 0,path, ans, s);

        return ans;
        
    }


    public void dfs(int i, int start, List<String> path, List<List<String>> ans, String s){
        if(i == s.length()){
            ans.add(new ArrayList<>(path));
            return;
        }

        //不分割
        if(i < s.length() -1){
            dfs(i+1, start,path,ans, s);
        }

        //分割

        if(ishuiwen(s,start,i)){
            path.add(s.substring(start,i+1));
            dfs(i+1,i+1,path,ans,s);
            path.removeLast();
        }

        
    }

    public boolean ishuiwen(String s, int left, int right){
        while(left < right){
            if(s.charAt(left) != s.charAt(right)){
                return false;
            }

            left++;
            right--;
        }

        return true;
    }
}

要点2:从答案视角,forj,i之间是否回文

java 复制代码
class Solution {
    public List<List<String>> partition(String s) {
        //选不选的视角
        List<List<String>> ans = new ArrayList<>();
        List<String> path = new ArrayList<>();

        dfs(0, path, ans, s);

        return ans;
        
    }


    public void dfs(int i, List<String> path, List<List<String>> ans, String s){
        if(i == s.length()){
            ans.add(new ArrayList<>(path));
            return;
        }

        //答案视角枚举子串结束的位置

        for(int j = i; j<s.length(); j++){
            if(ishuiwen(s,i,j)){
                path.add(s.substring(i, j+1));
                dfs(j+1,path,ans,s);
                path.removeLast();
            }
        }


        
    }

    public boolean ishuiwen(String s, int left, int right){
        while(left < right){
            if(s.charAt(left) != s.charAt(right)){
                return false;
            }

            left++;
            right--;
        }

        return true;
    }
}

随机知识

Agent Harness 在设计和实现上,确实学习(借鉴)了很多计算机操作系统中的核心概念。虽然两者的最终目标不同(一个管理硬件资源,一个编排智能体与环境的交互),但解决复杂系统问题时面临的挑战是相似的,因此很多设计思想和术语都被"借用"了过来。

下面我具体分析一下 Harness 从操作系统中"学习"了哪些概念,以及它们是如何映射的。

核心借鉴点对照表

操作系统概念 Agent Harness 中的对应 说明
进程/线程 Episode / Rollout / Trial 一次独立的 Agent-环境交互执行流,有独立的状态、上下文。Harness 管理多个 Episode 的并发或顺序执行。
调度器 步进循环(Step Loop) / 批处理调度 控制 Agent 和环境之间 step 的顺序、频率、优先级。例如,多个 Agent 共享一个环境时,需要调度谁先行动。
系统调用 step(action) -> (obs, reward, done, info) 这是 Harness 提供给 Agent 的统一抽象接口,屏蔽底层环境差异,就像系统调用屏蔽了硬件差异。
内存管理 & 地址空间 观察缓存(Observation Buffer) / 重放缓冲区(Replay Buffer) Harness 管理历史轨迹的存储、分配、回收,支持随机采样(类似页置换或堆管理)。
中断/异常处理 done 标志 / 超时处理 / 错误回调 当 episode 结束(环境抛出 done)或发生错误(环境崩溃、超时),Harness 会捕获并触发相应处理逻辑。
设备驱动程序 环境适配器(Environment Adapter) / Wrapper 将不同环境(Atari、机器人、Web API)的私有接口转换为 Harness 的统一接口,如同驱动隐藏硬件细节。
文件系统 实验日志 / 指标存储 / 检查点 Harness 提供持久化机制,保存 Agent 的状态、轨迹、评估结果,支持按时间或标签检索。
权限/安全隔离 动作屏蔽(Action Masking) / 沙箱 限制 Agent 的非法动作(如越界、危险指令),防止破坏环境或数据泄露,类似操作系统保护进程不互相干扰。
进程间通信 (IPC) Agent-环境之间的数据传递(通过 Harness 的中介队列) Harness 常使用通道(channel)或共享内存来高效传递观测和动作,解耦决策与执行。
死锁检测 & 超时 步超时 / 环境无响应处理 Harness 会监控每一步的执行时间,若 Agent 卡住或环境无响应,则重置或报错,避免整个实验僵死。

更深入的两个例子

1. "资源管理"的相似性

  • 操作系统:管理有限的物理资源(CPU时间、内存页、磁盘带宽),调度算法(CFS、FIFO、优先级)确保公平和效率。

  • Harness :管理有限的计算资源 (GPU显存、API调用次数、环境仿真时间)和数据资源(回放缓冲区大小)。需要决定:何时从缓冲区删除旧数据(类似页面置换,LRU常用),如何优先处理高回报的 episode(类似调度优先级)。

2. "抽象层"的分层设计

操作系统有清晰的用户态/内核态分层,系统调用是唯一跨越边界的方式。

Harness 也有类似分层:

  • 用户态(Agent 层) :策略网络、价值函数,只关心 (obs, reward),不知道底层环境的实现。

  • 内核态(Harness 层):环境接口、数据收集、错误恢复,提供稳定的执行环境。

这种分层强制隔离了关注点,使得 Agent 可以更换环境而无需修改代码------正如应用程序可以运行在不同硬件上而无需重写。

为什么 Harness 需要借鉴操作系统概念?

因为两者都要解决 "管理一个复杂、异步、并发、易出错的执行环境" 这个通用问题。操作系统为此积累了数十年的成熟方案(如调度、缓冲、错误隔离、资源抽象),Harness 工程直接复用这些思想,比自己从头发明要可靠和高效。

一个具体的开源例子

OpenAI Gym 的 Env 接口 + 运行脚本,其实就是一个极简的 Harness。但它没有内置复杂的调度和缓存。

更高级的框架如 RLlib (Ray) 中的 RolloutWorkerPolicyEvaluator,就明显借鉴了操作系统的概念:

  • 它使用 Actor 模型(类似进程)来并行执行多个 episode。

  • 它有一个 中心调度器 来分配 CPU/GPU 资源给不同的 RolloutWorker。

  • 它实现了 优先级重放缓冲区,允许更高 TD-error 的样本被更频繁采样(类似 I/O 优先级调度)。

总结

是的,Harness 工程大量借鉴了计算机操作系统的设计概念,尤其是在:抽象接口、资源管理、调度、并发控制、错误处理和持久化方面。理解操作系统原理,能帮助你更快地设计出高效、健壮的 Agent Harness。反过来,研究 Harness 也能让你更生动地体会到操作系统核心概念在另一个领域的应用价值。

KV Cache 隔离在大模型中的含义

一、什么是 KV Cache?

大模型在生成文本时,是一个token 一个 token 地自回归生成的。比如生成"我爱北京":

  1. 输入"我" → 计算所有层的 Key、Value → 生成"爱"

  2. 输入"我爱" → 按理说需要重新计算"我"和"爱"的所有 Key/Value,但这样计算量会随长度平方增长,极慢。

KV Cache 的核心思想 :把已经计算过的 token 的 Key 和 Value 缓存下来,生成新 token 时只计算新 token 的 Key/Value,然后与缓存的拼接,再计算注意力。这样计算量从 O(n2)O(n2) 降到 O(n)O(n)。

KV Cache 就是每一层中,已经处理过的 token 的 Key 向量和 Value 向量在内存中的存储。


二、什么是"隔离"?

在多用户、多请求或者复杂生成场景下,不同请求的 KV Cache 不能混在一起 ,否则会发生数据串扰、逻辑错误甚至安全泄露。隔离就是确保每个独立上下文的 KV Cache 被单独存储和管理,互不干扰


三、为什么需要 KV Cache 隔离?

1. 多用户服务(SaaS 场景)
  • 用户 A 和用户 B 同时向同一个模型发请求。如果不隔离,A 的缓存会被 B 的覆盖,或者互相污染。

  • 必须为每个请求/会话分配独立的缓存空间(通常是显存中的一块区域)。

  • Beam Search 中,每个 beam 有自己独立的生成历史。每个 beam 的 KV Cache 需要分开存储,不能混用。

  • 如果隔离不当,不同 beam 的注意力会串位,生成乱码。

3. 长上下文 / 滚动窗口
  • 一些系统只保留最近若干 token 的 KV Cache(滑动窗口)。需要能够独立管理每个请求的窗口边界。
4. 安全与隐私
  • 用户 A 的对话历史缓存不应被用户 B 读取。内存访问权限需要隔离。

四、隔离的工程实现方式

方式 描述 适用场景
请求级独立缓存 每个请求在显存中分配新的缓存区域(如 PyTorch 的 past_key_values 列表)。 最常用,如 Hugging Face Transformers 的 use_cache=True
PagedAttention(vLLM) 将 KV Cache 分页存储,不同请求的页物理隔离,逻辑上独立。支持高并发、动态扩容。 高吞吐量推理服务
Logical KV Cache 每个请求有一个逻辑 ID,映射到物理存储;不同 ID 的缓存不可互相访问。 分布式推理
内存池 + 引用计数 同一 prompt 前缀可以共享缓存(如多个用户问相同长文档),但一旦分支,则复制/隔离。 前缀缓存优化

五、不隔离会发生什么?

  • 内容混乱:用户 A 看到用户 B 的对话片段。

  • 生成错误:Beam 之间相互影响,导致重复或无效输出。

  • 性能下降:缓存相互覆盖,失去加速效果。

  • 安全漏洞:敏感信息可能泄露给其他用户。


六、面试中可能追问的延伸点

Q:KV Cache 隔离与 Function Calling 或 MCP 有直接关系吗?

A:没有直接关系,但属于大模型推理系统工程的核心问题。MCP 关注的是工具调用协议,而 KV Cache 隔离关注的是模型推理时的内存管理。两者都是构建高性能、可靠 Agent 的基础。

Q:vLLM 如何实现高效的 KV Cache 隔离?

A:vLLM 使用 PagedAttention,将缓存分成固定大小的块(pages),每个请求只持有指向这些块的逻辑映射。物理块可动态分配、重用,但不同请求的块不会混用。隔离由页表保证。

Q:在 Agent 系统中,KV Cache 隔离会影响工具调用的上下文吗?

A:会的。如果 Agent 需要并行执行多个工具调用,或同时维护多个对话分支,必须为每个分支独立管理 KV Cache,否则上下文会串扰。


七、一句话总结

KV Cache 隔离 = 在大模型推理中,为确保不同请求、不同 beam 或不同用户的生成历史互不干扰,对每一组 Key/Value 缓存进行独立存储和访问控制的技术。它是实现高并发、安全、稳定的大模型服务的基石之一。

Hook 是一个非常常见且强大的编程概念,中文常翻译为**"钩子"**。

简单理解:Hook 是一种在现有代码流程中"挂载"自定义代码的机制,允许你在某个事件发生前、后或替换其行为,而不用修改原有代码。


一、生活中的比喻

想象你有一台自动咖啡机,它有一个固定的工作流程:放豆子 → 磨粉 → 加水 → 萃取 → 出杯

你想在"加水"之后增加一步"加糖"。不拆机器改电路,而是利用咖啡机预留的一个**"定制接口"**,把你的"加糖"动作挂上去。咖啡机每次走到"加水"后自动执行你的"加糖"。

这个"定制接口"就是钩子 (Hook) ,你写的"加糖"代码就是钩子函数


二、技术上的定义

Hook 是一种允许开发者在不修改核心源代码的情况下,拦截、监听或扩展系统行为的技术手段。通常通过在特定位置(如函数调用前后、事件触发时)预留回调机制实现。

核心特征

  • 非侵入:不修改原有逻辑

  • 可扩展:动态添加额外行为

  • 解耦:核心系统与扩展功能分离


三、常见使用场景

1. 框架/库的生命周期钩子

很多框架提供钩子让用户在特定时机插入代码。

python

复制代码
# React 类组件生命周期钩子
componentDidMount() { 
  // 组件挂载后执行
}

# Vue 的 mounted 钩子
mounted() {
  console.log('组件已挂载')
}

2. 操作系统 / 底层编程

  • Windows 消息钩子:可以监听全局键盘/鼠标事件。

  • LD_PRELOAD (Linux):动态链接库劫持,可"钩住" libc 函数(如 openread)并添加日志或过滤。

3. Web 开发

  • Git Hooks :在 commitpush 等操作前后执行脚本(例如自动格式化、运行测试)。

4. 大模型 / Agent 开发中的回调钩子

在调用 LLM 时,可以设置钩子来:

钩子位置 典型用途
请求发送前 注入统一系统提示词、记录开始时间、修改参数
响应返回后 自动记录 token 消耗、过滤敏感词、缓存结果
工具调用前后 记录工具输入输出、实现重试机制、审计日志

LangChain 的回调示例

python

复制代码
from langchain.callbacks.base import BaseCallbackHandler

class MyCallback(BaseCallbackHandler):
    def on_llm_start(self, serialized, prompts, **kwargs):
        print("LLM 开始调用,prompt:", prompts)

    def on_llm_end(self, response, **kwargs):
        print("LLM 调用结束,响应:", response)

这种钩子在不改动 LangChain 核心代码的情况下,实现了可观测性(日志、监控)。


四、如何实现一个简单的 Hook(代码示例)

假设你有一个函数 greet,你想在不修改其源码的情况下,在它执行前后加日志。

python

复制代码
# 原始代码
def greet(name):
    return f"Hello, {name}!"

# 编写一个 Hook 函数
def hook_before(func):
    def wrapper(*args, **kwargs):
        print(f"调用 {func.__name__} 前,参数: {args}")
        result = func(*args, **kwargs)
        print(f"调用 {func.__name__} 后,结果: {result}")
        return result
    return wrapper

# 挂载 Hook(装饰器方式)
greet_with_hook = hook_before(greet)
greet_with_hook("World")

输出:

text

复制代码
调用 greet 前,参数: ('World',)
调用 greet 后,结果: Hello, World!

更先进的实现方式包括:事件监听器注册表切面编程 (AOP)中间件栈等。


五、Hook 与其他相似概念的区别

概念 特点 例子
Hook 预留的扩展点,可插入自定义逻辑 Git 钩子、React 生命周期
Callback 作为参数传入的函数,由框架在特定时机调用 setTimeout(callback, 1000)
Middleware 按顺序执行的钩子链,通常用于 HTTP 请求处理 Express 中间件
Event Listener 监听某个事件,事件触发时执行 DOM 的 click 监听器

Hook 可以看作是更通用的概念,回调、中间件等可以视为其特例。


六、在 MCP / Agent 开发中的具体用处

1. MCP 协议中

  • 可以在 MCP Client 发送请求前注入认证 token(before_request 钩子)。

  • 在收到服务器响应后自动脱敏敏感数据(after_response 钩子)。

2. Agent 推理循环

  • 工具调用前:校验参数合法性,若不合法可重写或拒绝。

  • 工具调用后:将结果格式化、截断、记录到记忆。

3. 上下文工程

  • 在构建 Prompt 前:通过钩子从记忆或 RAG 动态加载上下文。

  • 在模型输出后:通过钩子检测是否存在幻觉,触发重新生成。


七、面试中可能被追问的问题

Q:Hook 和 微调 的区别是什么?

A:Hook 是在不改变模型参数的前提下,运行时 插入逻辑;微调是改变模型内部权重,属于训练阶段。Hook 更轻量、灵活,适合动态策略,但无法改变模型的基础能力。

Q:Hook 会导致性能下降吗?

A:会。每次执行钩子函数都有额外开销。但合理的钩子设计(如异步执行、条件触发)可以将影响降到最低,通常远小于模型推理本身。

Q:常见的 Hook 滥用场景有哪些?

A:过多层钩子嵌套导致调用栈混乱;在钩子中执行耗时操作(如同步网络请求)拖慢核心流程;钩子之间的顺序依赖难以调试。


总结一句话

Hook 是系统预留的"插头",让你可以在不拆改内部电路的情况下,外挂自己的逻辑,从而实现可观测、可扩展、可定制的系统行为。在 AI Agent、框架开发、运维自动化中无处不在。

碎碎念:后续会更新每天学习的八股和算法 题,开始准备秋招的第25天。努力连续更新100天!以后每天就按,秋招项目【java+agent】,科研,必做项目,算法,八股,锻炼身体来总结。

总结:项目的代码起码还是要逻辑理清楚,看懂,不然一点下问题都要搞半天

1.算法要系统过一遍【灵神】14/27【早上】大概1.5h【要再整理一遍,感觉还是没有理解透彻】

2.秋招项目,【java】开始实际看业务,2.9/10;无【昨天睡前半小时?算是啊啊啊啊快看!!】

【agent】还在学,learncc大体看来一遍,打算整理一下,感觉看cc的性价比高很多,2h

3.科研要跑一下,无,【这个重要需要提高!!!】

4.检测项目也得总结文档,5h【这个和其他并行的】

5.训练项目,无

6.背八股,看看睡前能不能半小时吧

7.锻炼身体,1h

反思:今天和ai聊天写代码写到生气啊啊啊啊,时间不够用,好多事情没干,坚持坚持,规划好时间,少玩手机。第25天了,感觉还是啥也没干,每周周五下午就开始放纵自己,啊啊啊啊啊啊啊啊啊,学习呀学习。

相关推荐
fox_lht1 小时前
14.3.重构
开发语言·后端·rust
牛油果子哥q2 小时前
【C++ const 】全场景深度精讲:修饰规则、底层常量折叠、指针 / 引用 / 成员函数实战、易错坑点与工程代码实现
开发语言·c++
闪电悠米2 小时前
黑马点评-Redisson-02_reentrant_lock
java·spring boot·redis·分布式·缓存
云烟成雨TD2 小时前
Spring AI Alibaba 1.x 系列【67】ReactAgent SSE 流式输出
java·人工智能·spring
天天进步20152 小时前
Python全栈项目--社区问答平台
开发语言·python·django
我登哥MVP2 小时前
Spring Boo从“会用”到“精通”:Spring Boot 入门
java·spring boot·后端·spring·maven·intellij-idea·mybatis
skywalk81632 小时前
Tree-sitter是一个解析器生成器工具和一个增量解析库。它可以为源文件构建具体的语法树,并在编辑源文件时有效地更新语法树
开发语言·编程
染翰2 小时前
Java 实现 Git 自动克隆工具,打包成 Windows 独立 EXE(免安装JDK)
java·git·后端
AI视觉网奇2 小时前
Bambu Studio 发现 xx个开放边
开发语言·人工智能·python