力扣hot100-链表专题-刷题笔记(二)

题目1:合并两个有序链表(迭代版)

1、题目核心定义

  1. 问题描述:将两个升序排列的单链表合并为一个新的升序单链表;
  2. 输入:两个升序单链表的头节点 l1l2(可能为空);
  3. 输出:合并后的升序单链表的头节点。

2、核心逻辑(底层原理)

核心思想:双指针 + 虚拟头节点,分两步完成合并:

  1. 双指针遍历:用游标指针 prev 构建新链表,逐个比较 L1、L2 的当前节点,将更小的节点接入新链表;
  2. 处理剩余节点:若其中一个链表先遍历完,直接将另一个链表的剩余节点接在新链表末尾(原链表是升序,剩余节点必大于已合并部分)。

关键技巧:虚拟头节点(prehead)------ 避免单独处理 "新链表头节点" 的边界问题,简化代码逻辑。

3、标准模板代码(迭代版)

复制代码
/**
 * Definition for singly-linked list.
 * public class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode() {}
 *     ListNode(int val) { this.val = val; }
 *     ListNode(int val, ListNode next) { this.val = val; this.next = next; }
 * }
 */
class Solution {
    public ListNode mergeTwoLists(ListNode l1, ListNode l2) {
        // 虚拟头节点:简化头节点的边界处理
        ListNode prehead = new ListNode(-1);
        // 游标指针:用于构建新链表
        ListNode prev = prehead;

        // 第一步:双指针遍历,逐个接入更小的节点
        while (l1 != null && l2 != null) {
            if (l1.val <= l2.val) {
                prev.next = l1;  // 接入l1的当前节点
                l1 = l1.next;    // l1指针后移
            } else {
                prev.next = l2;  // 接入l2的当前节点
                l2 = l2.next;    // l2指针后移
            }
            prev = prev.next;    // 游标指针后移
        }

        // 第二步:处理剩余节点(最多一个链表有剩余)
        prev.next = (l1 == null)? l2 : l1;

        // 返回虚拟头节点的下一个节点(新链表的实际头)
        return prehead.next;
    }
}

4、代码关键细节

4.1 虚拟头节点(prehead)的作用
  • 解决 "空链表合并" 的边界问题:比如 L1=null 时,直接返回 L2(无需额外判断);
  • 统一新链表的构建逻辑:所有节点都通过 prev.next 接入,无需单独处理 "第一个节点"。

✅ 关键:虚拟头节点不是 null (如果是 null,就没法通过 prev.next 接第一个节点了),必须是一个实实在在的节点,只是值无意义。

4.2 双指针遍历的核心规则
  • 循环条件 l1 != null && l2 != null:仅处理两个链表都有节点的情况;
  • 比较逻辑 l1.val <= l2.val:保证新链表的升序性(相等时优先接 l1,不影响结果);
  • 指针移动:接入节点后,对应链表的指针和游标指针都要后移,保证遍历不遗漏。
4.3 剩余节点的处理
  • 逻辑:prev.next = l1 == null ? l2 : l1------ 利用三元运算符,一行代码处理 "剩余节点接入";
  • 正确性:原链表是升序,剩余节点的所有值都大于已合并部分,直接接入不破坏升序。

5、复杂度分析

维度 复杂度 说明
时间复杂度 O(n+m) n、m 是两个链表的长度,需遍历所有节点
空间复杂度 O(1) 仅用虚拟头节点和游标指针,无额外空间

6、典型场景验证

l1=1→2→8→9l2=1→3→5→7 为例:

  1. 双指针遍历阶段:依次接入 1(l1)1(l2)2(l1)3(l2)5(l2)7(l2)
  2. 剩余节点阶段:l1 剩余 8→9,接入新链表末尾;
  3. 最终结果:1→1→2→3→5→7→8→9,严格升序。

7、面试答题话术

"我采用双指针 + 虚拟头节点的迭代法合并两个有序链表:

  1. 用虚拟头节点简化头节点的边界处理,用游标指针构建新链表;
  2. 双指针遍历两个链表,逐个将更小的节点接入新链表;
  3. 遍历结束后,将剩余未处理的链表直接接在新链表末尾;
  4. 该方法时间复杂度 O (n+m)、空间复杂度 O (1),无递归栈溢出风险,是更实用的生产级写法。"
总结

这个迭代版本的优势非常明显:逻辑清晰、无栈溢出、空间效率高,是合并有序链表的 "最优实践";核心细节(虚拟头节点、剩余节点处理)是链表操作的通用技巧,掌握后可以解决大部分链表拼接类问题。

✅ 虚拟头节点是「真实存在的节点」(有自己的 val,比如 - 1),也确实是新链表物理上的第一个节点;

✅ 但我们只把它当工具 ,最终返回时直接跳过它(取prehead.next),让它的下一个节点成为新链表 "逻辑上的头节点",相当于 "用完就扔"。

复制代码
物理链表(真实存在):-1 → 1 → 1 → 2
逻辑链表(我们要的结果):1 → 1 → 2
  • 虚拟头节点:值任意(-1 只是习惯)、非 null,作用是简化头节点边界处理;
  • 游标操作顺序:先 prev.next = 节点 接元素,再 prev = prev.next 移游标;
  • 剩余节点:利用三元运算符一步接入,依赖 "原链表升序" 的前提;
  • 返回值:必须返回 prehead.next(跳过虚拟头节点);
  • 游标节点(prev):核心作用是 "逐个拼接节点,构建新链表"。

题目 2:两数相加(迭代版)

1、题目核心定义

问题描述:将两个逆序存储数字的单链表,按位相加后返回逆序存储结果的单链表(链表逆序存储:个位在表头,百位在表尾);输入:两个逆序存储数字的单链表的头节点 l1、l2(可能为空,节点值为 0-9 的整数);输出:逆序存储相加结果的单链表的头节点。

2、核心逻辑(底层原理)

核心思想:双指针 + 虚拟头节点 + 进位变量,分两步完成相加

疑惑 1:为啥不用反转链表?直接算结果一样吗?

解答:逆序链表天生契合竖式加法规则(表头就是个位,不用从尾开始算),直接遍历相加和手工列竖式顺序完全一致;反转链表反而要多做两次反转操作,增加时间复杂度,属于画蛇添足。

  1. 双指针遍历:用游标指针 cur 构建新链表,逐个取 L1、L2 的当前节点值(空节点补 0),计算「当前位和 + 进位」,将个位值接入新链表,更新进位;
  2. 处理尾进位:若遍历完所有节点后仍有进位(carry=1),需单独将进位节点接在新链表末尾(唯一区别于合并链表的核心逻辑)。

关键技巧:

  1. 虚拟头节点(dummy)------ 避免单独处理 "新链表头节点" 的边界问题,简化代码逻辑;
  2. 进位变量(carry)------ 模拟手工竖式加法的 "满 10 进 1" 规则,处理跨位进位。

3、标准模板代码(迭代版)

java 复制代码
/**
 * Definition for singly-linked list.
 * public class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode() {}
 *     ListNode(int val) { this.val = val; }
 *     ListNode(int val, ListNode next) { this.val = val; this.next = next; }
 * }
 */
class Solution {
    public ListNode addTwoNumbers(ListNode l1, ListNode l2) {
        // 虚拟头节点:简化头节点的边界处理
        ListNode dummy = new ListNode(0);
        // 游标指针:用于构建新链表
        ListNode cur = dummy;
        // 进位变量:模拟竖式加法的"满10进1"
        int carry = 0;

        // 第一步:双指针遍历,逐个计算并接入当前位结果
        // 疑惑2:为啥循环条件要加 carry != 0?
        // 解答:为了处理"所有位数加完但还有进位"的边界场景(比如999+999=1998),少了这个条件会漏掉最后一位进位
        while (l1 != null || l2 != null || carry != 0) {
            // 疑惑3:这行代码啥意思?
            // 解答:给短链表补0,避免位数不够加不了(比如99+999,加第三位时99没数了就补0)
            int num1 = l1 != null ? l1.val : 0;
            int num2 = l2 != null ? l2.val : 0;
            
            // 疑惑4:这段数学逻辑为啥这么算?
            // 解答:完全模拟手工竖式加法:sum=当前位和+上一位进位;carry=sum/10取进位(0或1);curVal=sum%10取个位
            int sum = num1 + num2 + carry;
            carry = sum / 10;             // 更新进位(0或1)
            int curVal = sum % 10;        // 当前位结果(取个位)
            
            cur.next = new ListNode(curVal);  // 接入当前位结果节点
            cur = cur.next;                  // 游标指针后移

            // 思考:l1/l2遍历完后,这里就不会执行了,但进位还在,所以必须靠carry != 0兜底
            if (l1 != null) l1 = l1.next;
            if (l2 != null) l2 = l2.next;
        }

        // 第二步:尾进位已在循环中处理(无需额外代码)
        // 思考:这里和合并链表的"处理剩余节点"不同,合并是接剩余链表,这里是靠循环条件处理尾进位

        // 返回虚拟头节点的下一个节点(新链表的实际头)
        return dummy.next;
    }
}

4、代码关键细节

4.1 虚拟头节点(dummy)的作用

解决 "空链表相加" 的边界问题:比如 L1=null、L2=[0] 时,直接通过循环逻辑返回 [0](无需额外判断);统一新链表的构建逻辑:所有结果节点都通过 cur.next 接入,无需单独处理 "第一个节点"。✅ 关键:虚拟头节点不是 null,必须是一个实实在在的节点(值设 0 仅为占位,无业务意义),否则无法通过 cur.next 接第一个结果节点。

4.2 双指针遍历的核心规则

循环条件 l1 != null || l2 != null || carry != 0

  • l1 != null || l2 != null:处理两个链表的所有节点(长短链表自动补 0);
  • carry != 0:兜底 "所有节点遍历完但仍有进位" 的边界场景(如 999+999);数值处理逻辑:
  • 空节点补 0:num1 = l1 != null ? l1.val : 0,保证位数不同时能正常相加;
  • 求和规则:sum = num1 + num2 + carry,必带上一轮进位,符合竖式加法规则;
  • 结果拆分:carry = sum / 10(取进位)、curVal = sum % 10(取个位),仅处理 0-9 的节点值;指针移动:接入结果节点后,游标指针必后移,原链表指针仅非空时后移,保证遍历不遗漏。

**核心总结:**首次运算 carry=0 → sum = 两数之和 → carry=sum/10 判断是否进位 → curVal=sum%10 是当前节点值 → 下次运算 sum 会带上进位,完美契合手工加法。

4.3 尾进位的处理

逻辑:尾进位已融合在循环条件中(carry != 0),无需额外代码;正确性:所有节点遍历完后,若 carry=1,会触发最后一次循环,生成值为 1 的节点并接入,符合 "满 10 进 1" 规则。

**思考:**百位算完后 l1/l2 都变成 null,不会再后移,但进位 1 还在,此时靠 carry != 0 进入循环,补全最后一个节点。

对比维度 合并两个有序链表 两数相加
核心框架 虚拟头节点 + 双指针遍历 虚拟头节点 + 双指针遍历(完全一致)
循环条件 l1 != null && l2 != null l1 != null || l2 != null || carry!=0
节点处理逻辑 选更小的节点接入 算和 + 进位,取个位接入
收尾逻辑 接剩余链表 处理尾进位(循环内完成)
返回值 prehead.next dummy.next(完全一致)

5、复杂度分析

维度 复杂度 说明
时间复杂度 O(n+m) n、m 是两个链表的长度,最多遍历 max(n,m)+1 次(含尾进位)
空间复杂度 O(1) 仅用虚拟头节点、游标指针、进位变量,结果链表是输出要求不计入

6、典型场景验证

以 l1=[9,9,9,9,9,9,9](9999999)、l2=[9,9,9,9](9999)为例:

你的疑惑验证: 这个例子刚好触发尾进位,完美验证 carry != 0 的作用双指针遍历阶段:

  1. 个位:9+9+0=18 → 接入 8,carry=1;
  2. 十位:9+9+1=19 → 接入 9,carry=1;
  3. 百位:9+9+1=19 → 接入 9,carry=1;
  4. 千位:9+9+1=19 → 接入 9,carry=1;
  5. 万位:9+0+1=10 → 接入 0,carry=1;
  6. 十万位:9+0+1=10 → 接入 0,carry=1;
  7. 百万位:9+0+1=10 → 接入 0,carry=1;尾进位处理阶段:
  8. 所有节点遍历完,carry=1 → 触发循环,sum=0+0+1=1 → 接入 1,carry=0;最终结果:[8,9,9,9,0,0,0,1](对应 10009998),完全符合加法规则。

7、面试答题话术

"我采用双指针 + 虚拟头节点 + 进位变量的迭代法实现两数相加:

  1. 利用链表逆序存储的特性,无需反转链表,直接从表头(个位)开始相加,契合竖式加法规则;
  2. 用虚拟头节点简化头节点边界处理,用游标指针构建结果链表,用进位变量模拟'满 10 进 1';
  3. 循环条件加入 carry != 0 兜底尾进位场景,避免漏掉最后一位;
  4. 空节点自动补 0,保证长短链表都能正常相加;该方法时间复杂度 O (n+m)、空间复杂度 O (1),无递归栈溢出风险,是处理链表加法的最优实践。"
总结

这个迭代版本的优势:逻辑清晰(完全贴合手工加法规则)、无栈溢出、空间效率高,是两数相加的 "最优实践";核心细节(虚拟头节点、进位处理、空节点补 0)是链表数值运算的通用技巧,掌握后可解决大部分链表加法 / 减法类问题。

✅ 你的所有思考闭环:

  • 不用反转链表 → 逆序契合加法规则;
  • 补 0 代码 → 给短链表补位;
  • 数学逻辑 → 模拟竖式加法;
  • carry != 0 → 处理尾进位;
  • 和合并链表的关联 → 同框架,不同节点逻辑。

题目3:删除链表的倒数第 n 个节点

1、题目核心定义

问题描述:给定一个单链表的头节点head,删除链表的倒数第 n 个节点 ,并返回修改后的链表头节点。输入:单链表头节点head(节点值为整数,可能为空)、整数n(保证 1≤n≤链表长度);输出:删除指定节点后的链表头节点。

2、核心逻辑(底层原理)

核心思想:双指针 + 虚拟头节点,分两步完成删除

  • 疑惑 1:为啥不用 "先遍历数长度,再遍历删节点"?解答:双指针法只需遍历一次链表(时间复杂度更优),避免两次遍历的冗余操作;虚拟头节点统一 "删头节点" 和 "删中间节点" 的逻辑。
  1. 双指针拉开间距:快指针先出发走n步,与慢指针形成n个节点的固定间距;
  2. 同步遍历定位前驱:快慢指针同速遍历,快指针到链表末尾时,慢指针停在 "倒数第 n 个节点的前驱节点";
  3. 删除目标节点:修改慢指针的next指向,跳过目标节点。

关键技巧:

  1. 虚拟头节点(dummy)------ 避免单独处理 "删除头节点" 的边界问题,简化代码逻辑;
  2. 双指针固定间距 ------ 一次遍历定位目标节点的前驱,保证时间效率。

3、标准模板代码

java 复制代码
/**
 * Definition for singly-linked list.
 * public class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode() {}
 *     ListNode(int val) { this.val = val; }
 *     ListNode(int val, ListNode next) { this.val = val; this.next = next; }
 * }
 */
class Solution {
    public ListNode removeNthFromEnd(ListNode head, int n) {
        // 虚拟头节点:统一头节点与中间节点的删除逻辑
        ListNode dummy = new ListNode(0, head);
        // 双指针初始化:均从虚拟头节点出发
        ListNode fast = dummy;
        ListNode slow = dummy;

        // 第一步:快指针先走n步,拉开n个节点的间距
        for (int i = 0; i < n; i++) {
            fast = fast.next;
        }

        // 第二步:快慢指针同步遍历,直到快指针到末尾
        while (fast.next != null) {
            fast = fast.next;
            slow = slow.next;
        }

        // 第三步:删除目标节点(慢指针的next即为倒数第n个节点)
        slow.next = slow.next.next;

        // 返回新链表的实际头节点
        return dummy.next;
    }
}

4、代码关键细节

4.1 虚拟头节点(dummy)的作用

解决 "删除头节点" 的边界问题:比如链表为[1]、删除倒数第 1 个节点时,无需单独修改head指针;统一链表的修改逻辑:所有节点的删除都通过 "修改前驱节点的 next" 完成,无需区分头 / 中间节点。✅ 关键:虚拟头节点是一个实际节点(值设为 0 仅占位),保证slow.next能正常操作第一个节点。

4.2 双指针遍历的核心规则
  • 快指针先走n步:制造 "n 个节点的固定间距",为后续定位做准备;
  • 循环条件fast.next != null:快指针停在 "最后一个有效节点" 时,慢指针恰好停在 "目标节点的前驱"(避免多走一步);
  • 指针移动逻辑:快指针先走n步后,快慢指针同速移动,保证间距始终为n
4.3 目标节点的定位逻辑

数学关系:慢指针位置 = 链表长度 - n → 慢指针的next即为 "倒数第 n 个节点"。例:链表[1→2→3→4→5]n=2:快指针先走 2 步→停在2;同步遍历后快指针停在5,慢指针停在33的next4(倒数第 2 个节点),直接删除。

5、复杂度分析

维度 复杂度 说明
时间复杂度 O(L) L 为链表长度,仅遍历一次
空间复杂度 O(1) 仅用虚拟头节点和双指针,无额外空间

6、典型场景验证

链表[1→2→3→4→5]、删除倒数第2个节点为例:

  1. 快指针先走 2 步:从dummy12
  2. 同步遍历:快指针→345fast.next=null停止),慢指针→123
  3. 删除节点:3的next指向5,节点4被删除;
  4. 结果:[1→2→3→5],符合预期。

7、面试答题话术

我采用双指针 + 虚拟头节点的方法实现 "删除链表倒数第 n 个节点":

  1. 利用虚拟头节点简化 "删除头节点" 的边界处理,避免单独分支判断;
  2. 让快指针先出发走n步,与慢指针形成固定间距;
  3. 快慢指针同速遍历,快指针到末尾时,慢指针恰好定位到目标节点的前驱;
  4. 修改慢指针的next完成删除,该方法时间复杂度 O (L)、空间复杂度 O (1),是最优实践。

总结

这个模板的优势:逻辑清晰(基于 "固定间距定位")、无冗余操作、覆盖所有边界场景;核心细节(虚拟头节点、双指针间距)是链表 "定位 + 修改" 类题目的通用技巧,掌握后可解决 "找链表中点""删除指定节点" 等问题。

题目4:两两交换链表中的节点

1、题目核心定义

问题描述:给定一个单链表的头节点head两两交换 链表中相邻的节点,返回交换后的链表头节点(要求:不能修改节点的值,只能修改指针指向;不新建链表,仅原地操作)。输入:单链表头节点head(节点值为整数,可能为空 / 只有 1 个节点);输出:交换后的链表头节点。

示例:

  • 输入:1→2→3→4 → 输出:2→1→4→3
  • 输入:1→2→3 → 输出:2→1→3
  • 输入:1/null → 输出:1/null

2、核心逻辑(底层原理)

核心思想:虚拟头节点 + 锚点指针 + 局部指针交换,把 "整体交换" 拆成 "逐对交换"

  1. 锚点定位:用cur指针作为 "锚点",始终指向 "待交换两个节点的前驱",保证交换时不丢失链表上下文;
  2. 局部交换:对每一对节点,通过 3 步指针操作完成交换(仅修改指向,不新建节点);
  3. 迭代推进:交换完一对后,把cur移到这对节点的末尾,继续处理下一对,直到无完整节点对可交换。

关键技巧:

  1. 虚拟头节点(dummy)------ 统一 "交换头节点" 和 "交换中间节点" 的逻辑,避免单独处理边界;
  2. 临时指针(post)------ 保存待交换的第一个节点,避免交换时指针丢失,导致链表断裂;
  3. 逐对处理 ------ 把复杂问题拆分为 "重复处理两个节点",降低逻辑复杂度。

3、标准模板代码

java 复制代码
/**
 * Definition for singly-linked list.
 * public class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode() {}
 *     ListNode(int val) { this.val = val; }
 *     ListNode(int val, ListNode next) { this.val = val; this.next = next; }
 * }
 */
class Solution {
    public ListNode swapPairs(ListNode head) {
        // 1. 虚拟头节点:统一交换逻辑,避免单独处理头节点
        ListNode dummy = new ListNode(0, head);
        // 2. 锚点指针:始终指向待交换节点对的前驱,初始在dummy
        ListNode cur = dummy;
        // 3. 临时指针:保存待交换的第一个节点,避免指针丢失
        ListNode post = null;

        // 4. 循环条件:确保有至少两个节点可交换(避免空指针)
        while (cur.next != null && cur.next.next != null) {
            // 5. 保存待交换的第一个节点
            post = cur.next;
            
            // 6. 核心三步:交换当前两个节点(仅修改指针指向)
            cur.next = post.next;        // 步骤1:前驱指向第二个节点
            post.next = post.next.next;  // 步骤2:第一个节点指向第二个节点的后继
            cur.next.next = post;        // 步骤3:第二个节点指向第一个节点
            
            // 7. 锚点后移:处理下一对节点
            cur = post;
        }

        // 8. 返回交换后的链表头(跳过虚拟头节点)
        return dummy.next;
    }
}

4、代码关键细节

4.1 虚拟头节点(dummy)的作用
  • 解决 "交换头节点" 的边界问题:比如交换1→2时,无需单独修改head指针,直接通过dummy.next指向 2 即可;
  • 保证链表上下文连续:无论交换哪一对节点,都能通过cur(锚点)关联到前序链表,避免链表断裂;
  • 最终返回dummy.next:直接得到交换后的新链表头,无需额外判断。

✅ 关键:dummy 仅为 "占位锚点",不参与实际业务逻辑,值设为 0 仅为规范。

4.2 核心三步交换的逻辑拆解(以cur→a→b→c为例)
操作步骤 代码 作用(对应a和b交换)
步骤 1 cur.next = post.next cur→b(把 b 提到 a 前面)
步骤 2 post.next = post.next.next a→c(a 指向 b 的原后继,保留后续链表)
步骤 3 cur.next.next = post b→a(b 指向 a,完成 a 和 b 的交换)

交换后结构:cur→b→a→c,链表上下文完全保留。

4.3 循环条件的必要性

cur.next != null && cur.next.next != null

  • 必须同时满足 "有第一个节点" 和 "有第二个节点",才执行交换;
  • 若只判断cur.next != null,当只剩 1 个节点时,cur.next.next会触发空指针异常;
  • 边界适配:自动处理 "节点数为奇数" 的情况(最后一个节点不交换)。
4.4 锚点指针cur的移动逻辑

cur = post

  • post是交换后的第二个节点(比如 a),把cur移到这里,下一轮循环就能以 a 为前驱,处理下一对节点(c 和 d);
  • 保证每一轮循环的 "锚点位置" 一致(待交换节点对的前驱),逻辑统一。

5、复杂度分析

维度 复杂度 说明
时间复杂度 O(n) n 为链表长度,仅遍历一次
空间复杂度 O(1) 仅用虚拟头节点和 3 个指针,无额外空间

6、典型场景验证(输入1→2→3→4

  1. 初始状态:dummy→1→2→3→4cur=dummy
  2. 第一次交换:
    • post=1cur.next=21.next=32.next=1cur=1
    • 链表变为:dummy→2→1→3→4
  3. 第二次交换:
    • post=3cur.next=43.next=null4.next=3cur=3
    • 链表变为:dummy→2→1→4→3
  4. 循环终止(cur.next=null),返回dummy.next=2,结果符合预期。

7、面试答题话术

我采用虚拟头节点 + 锚点指针的方法实现 "两两交换链表节点":

  1. 用虚拟头节点统一交换逻辑,避免单独处理头节点的边界问题;
  2. cur为锚点指针,始终指向待交换节点对的前驱,保证链表上下文连续;
  3. 对每一对节点,通过 3 步指针操作完成原地交换(仅修改指向,不新建节点);
  4. 迭代推进锚点指针,直到无完整节点对可交换,该方法时间复杂度 O (n)、空间复杂度 O (1),是最优实践。

总结

  1. 核心逻辑:把 "整体交换" 拆分为 "逐对交换",每次仅处理两个节点,通过 3 步指针操作完成原地交换;
  2. 关键技巧:虚拟头节点简化边界、临时指针避免链表断裂、锚点指针保证迭代推进;
  3. 空间优势:无新建链表 / 节点,仅用常数级额外空间,是链表交换类题目的通用最优解法。
相关推荐
曾几何时`2 小时前
MySQL(四)表的约束
算法
gihigo19982 小时前
竞争性自适应重加权算法
人工智能·算法·机器学习
航Hang*2 小时前
Photoshop 图形与图像处理技术——第9章:实践训练6——滤镜特效
图像处理·笔记·学习·ui·photoshop
明洞日记2 小时前
【CUDA手册004】一个典型算子的 CUDA 化完整流程
c++·图像处理·算法·ai·图形渲染·gpu·cuda
金色光环2 小时前
【SCPI学习】STM32与LWIP实现SCPI命令解析
stm32·嵌入式硬件·算法·scpi学习·scpi
豆沙沙包?2 小时前
2026年--Lc342-841. 钥匙和房间(图 - 广度优先搜索)--java版
java·算法·宽度优先
Emilin Amy3 小时前
【C++】【STL算法】那些STL算法替代的循环
开发语言·c++·算法·ros1/2
IMPYLH3 小时前
Lua 的 String(字符串) 模块
开发语言·笔记·单元测试·lua
Hcoco_me3 小时前
大模型面试题74:在使用GRPO训练LLM时,训练数据有什么要求?
人工智能·深度学习·算法·机器学习·chatgpt·机器人