【算法篇】5.链表

链表

面试手撕专用

java 复制代码
import java.util.*;

// 链表节点(面试必须自己写)
class ListNode {
    int val;
    ListNode next;
    ListNode(int val) { this.val = val; }
}
public class Main {  // 注意:ACM 类名必须是 Main!!!
    public static void main(String[] args) {
        // 1. 输入
        Scanner sc = new Scanner(System.in);
        int n = sc.nextInt();
        int[] nums = new int[n];
        for (int i = 0; i < n; i++) nums[i] = sc.nextInt();

        // 2. 构建链表
        ListNode head = buildList(nums);

        // 3. 调用你的算法
        //ListNode newHead = sortList(head);

        // 4. 输出结果(自测看结果)
        printList(newHead);
    }
    // 构建链表
    public static ListNode buildList(int[] nums) {
        ListNode dummy = new ListNode(0);
        ListNode cur = dummy;
        for (int num : nums) {
            cur.next = new ListNode(num);
            cur = cur.next;
        }
        return dummy.next;
    }

    // 打印链表(自测用)
    public static void printList(ListNode head) {
        while (head != null) {
            System.out.print(head.val + " ");
            head = head.next;
        }
    }
}

2. 两数相加

注意分别处理 【相同数位上的两数之和 val1 + val2,并加上上一轮新产生的进位值 carry:sum = val1 + val2 + carry】 与 【这一轮新产生的进位值 carry = carry / 10】。

并且当两链表 l1 和 l2 都遍历完后,记得额外处理最后的一次进位。例如:99+9=108,这里需要单独处理百位最后的1。

一句话理解:

  • 链表是倒着存 的:2→4→3 代表 342
  • 按位相加,处理进位,最后输出新链表

核心思路

  1. 同时遍历两个链表,对应位相加 + 进位
  2. 每一位结果:sum % 10
  3. 新进位:sum / 10
  4. 链表走完但还有进位,要多补一个节点

注意:

  • 用ret 虚拟头节点方便返回结果
  • 循环条件: **l1不空 || l2不空 || 有进位**
  • 每一位求和、算进位、建节点
  • 最后返回 **ret.next**
java 复制代码
class Solution {
    public ListNode addTwoNumbers(ListNode l1, ListNode l2) {
        if(l1==null) return l2;
        if(l2==null) return l1;

        int next=0,sum=0;
        ListNode ret=new ListNode(-1);
        ListNode cur=ret;
        //加完为止(避免单独处理进位)
        while(l1!=null||l2!=null||next!=0){
            //不为空
            int a=l1==null?0:l1.val;
            int b=l2==null?0:l2.val;
            sum=next+a+b;
            //加节点
            ListNode node=new ListNode(sum%10);
            next=sum/10;
            cur.next=node;
            cur=cur.next;
            //不为空,才下一位
            if(l1!=null) l1=l1.next; 
            if(l2!=null) l2=l2.next;
        }

        return ret.next;
    }
}

24. 两两交换链表中的节点

非递归版

不能只交换值,必须改指针

我们用 虚拟头节点 dummy,让所有节点处理方式统一。

假设当前结构:dummy -> 1 -> 2 -> 3 -> 4

要交换 1 和 2,只需要 3 步:

  1. dummy.next = 2
  2. 1.next = 2.next
  3. 2.next = 1

然后把 dummy 移动到下一组的前一个节点(即 1),继续循环。

思路:

cur 指前

first 是第一个

second 是第二个

cur 指向 second

first 指向 second 下一个

second 指向 first

cur 跳到 first

java 复制代码
class Solution {
    public ListNode swapPairs(ListNode head) {
        //0个或者1个反转不了
        if(head==null||head.next==null) return head;
        //虚拟头,更方便
        ListNode dummy=new ListNode(-1);
        dummy.next=head;
        ListNode cur=dummy;
        //有两个节点,才能交换
        while(cur.next!=null&&cur.next.next!=null){
            //dummy->first->second
            ListNode first=cur.next;
            ListNode second=cur.next.next;

            //交换
            first.next=second.next;
            second.next=first;
            cur.next=second;

            //下一个: dummy->second->first
            //原来的第一个 就是后面一个了
            cur=first;
        }
        return dummy.next;
    }
}

递归版

java 复制代码
class Solution {
    public ListNode swapPairs(ListNode head) {
        //0个或者1个反转不了
        if(head==null||head.next==null) return head;

        ListNode dummy=new ListNode(-1);
        dummy.next=head;

        ListNode cur=dummy;
        while(cur.next!=null&&cur.next.next!=null){
            //node1->node2
            ListNode node1=cur.next;  //第一个
            ListNode node2=cur.next.next;//第二个

            //交换后  node2->node1
            cur.next=node2;
            node1.next=node2.next;
            node2.next=node1;

            cur=node1; //下一个cur(node1已经交换到后面一个去了)
        }
        return dummy.next;
    }
}

分类1

876. 链表的中间结点

给你一个链表,返回中间结点

  • 偶数个结点:返回第二个中间结点
  • 奇数个结点:返回正中间

例子:

复制代码
1->2->3->4->5` → 返回 `3
1->2->3->4->5->6` → 返回 `4

java 复制代码
class Solution {
    //快慢指针
    public ListNode middleNode1(ListNode head) {
        ListNode slow=head;
        ListNode fast=head;
        //多走一步
        while(fast!=null&&fast.next!=null){
            slow=slow.next;         //走一步
            fast=fast.next.next;    //走两步
        }
        return slow;
    }
}

206. 反转链表

非递归版本:

🧠 核心思路(就 3 步)

你只要记住一句话:每个节点的 next 指向前一个,从头到尾改一遍,最后返回新头。

具体 3 步

prev = 前一个节点(一开始是 null)

cur = 当前节点(从 head 开始)

循环:

保存下一个节点 next = cur.next

当前节点指向前一个 curr.next = prev

prev 往前走(变成当前节点)

cur 往前走(变成刚才保存的 next)

java 复制代码
class Solution {
    public ListNode reverseList(ListNode head) {
        if(head==null) return null;

        ListNode prev=null;
        ListNode cur=head;
        //当前需要反转的节点不为空,就继续
        while(cur!=null){
            ListNode next=cur.next; //记录下一个
            cur.next=prev;          //反转

            //下一组
            prev=cur;
            cur=next;
        }
        //cur此时为空,prev是新头
        return prev;
    }
}

递归版本:

java 复制代码
class Solution {
    public ListNode reverseList(ListNode head) {
        if(head==null||head.next==null){
            return head;
        }
        ListNode newHead=reverseList(head.next);

        head.next.next=head;
        head.next=null;

        return newHead;

    }
}

234. 回文链表(中点/反转)

核心思路

找中点:快慢指针找链表中点

反转后半段:把链表后半段反转

一一比较:前半段 和 反转后的后半段 逐个比对

全相同 = 回文

有不同 = 不是

为什么必须用 **while(right != null)** 判断,不能用 left

核心:

**因为反转后的右半段,一定比左半段短(或相等)**用短的那边做循环判断,永远不会空指针!

\1. 先看链表结构

回文链表分两种:

① 偶数长度:1 → 2 → 2 → 1

中点是第 2 个节点

  • 左半段:1 → 2
  • 右半段:2 → 1左右一样长

② 奇数长度:1 → 2 → 3 → 2 → 1

中点是第 3 个节点

  • 左半段:1 → 2 → 3
  • 右半段:1 → 2右半段 更短!
java 复制代码
class Solution {
    public boolean isPalindrome(ListNode head) {
        if(head==null) return false;
        //找中点
        ListNode slow=head;
        ListNode fast=head;
        while(fast!=null&&fast.next!=null){
            slow=slow.next;
            fast=fast.next.next;
        }
        //两个链表天然断开
        //反转右半部分
        ListNode right=reverse(slow);
        //开始对比
        ListNode left=head;
        //使用右半部分判断,不会空指针异常
        // 左半部分>=右(中点方法)
        while(right!=null){
            if(right.val!=left.val) return false;
            right=right.next;
            left=left.next;
        }
        return true;
    }
    public ListNode reverse(ListNode node){
        if(node==null) return null;

        ListNode cur=node,prev=null;
        while(cur!=null){
            ListNode next=cur.next;

            cur.next=prev;
            prev=cur;
            cur=next;
        }
        return prev; //这个是新的头
    }
}

143. 重排链表(中点/反转/交叉)

1. 找中点(快慢指针)

把链表切成前半段和后半段

slow 走一步

fast 走两步

最后 slow 停在前半段最后一个节点

2. 反转后半段

把后半段倒过来,方便从后往前取节点

3. 交叉合并

一个取前半,一个取后半,交替拼接

前半段:1 → 2 →

反转后半段:4 → 3 →

合并:1 → 4 → 2 → 3

疑惑?

为什么循环条件不一样?

找中点(876)

希望 fast 走到链表最后一位 这样 slow 才能走到正中间

所以循环条件要允许 fast 走到最后(多走一点,条件不要太远 )**:**fast != null && fast.next != null

切链表(重排)

我们不希望 slow 走到中间而是希望 slow 停在左半段最后一个方便把链表切成两半。

**所以要提前一步停下:**fast.next != null && fast.next.next != null

java 复制代码
class Solution {
    public void reorderList(ListNode head) {
        if(head==null) return;
        ListNode slow=head;
        ListNode fast=head;
        //1.找中点
        while(fast.next!=null&&fast.next.next!=null){
            slow=slow.next;         //走一步
            fast=fast.next.next;    //走两步
        }
        //slow在前半段最后一个点  下一个点是后半段的第一个点

        //2.反转后半段
        ListNode cur=slow.next,prev=null;
        slow.next=null; //断开
        while(cur!=null){ //当前需要反转的不为空
            ListNode next=cur.next;
            cur.next=prev;
            prev=cur;
            cur=next;
        }
        //prev就是反转后新的头
        //3.交叉合并
        ListNode l1=head;
        ListNode l2=prev;
        while(l2!=null){
            //记录下一个需要交叉节点
            ListNode n1=l1.next;
            ListNode n2=l2.next;
            //交叉
            l1.next=l2;
            l2.next=n1;
            //下一个
            l1=n1;
            l2=n2;
        }

    }
}

92. 反转链表 II(反转指定区间)

核心思路:

找到区间 → 反转区间 → 拼接回去和 K 个一组反转逻辑几乎一样

java 复制代码
class Solution {
    public ListNode reverseBetween(ListNode head, int left, int right) {

        ListNode dummy=new ListNode(-1);
        dummy.next=head;
        ListNode prev=dummy; //记录前一个
        //链表开始的上一个节点
        for(int i=1;i<left;i++) prev=prev.next; 
        ListNode start=prev.next;//开始位置
        ListNode end=prev;      //记录前一个
        for(int i=left;i<=right;i++) end=end.next;//结束位置
        ListNode next=end.next; //记录后一个
        //反转
        ListNode cur=start,pre=null;
        while(cur!=null){
            ListNode ne=cur.next; //下一个需要反转的节点
            cur.next=pre;
            //下一组
            pre=cur;
            cur=ne;
        }
        //拼接前后(反转后pre变头,start变尾巴)
        prev.next=pre;
        start.next=next;
        return dummy.next;
    }
}

25. K 个一组翻转链表

🧠 核心思路(超级大白话)

  • 把链表每 K 个分成一组
  • 每组内部反转
  • 反转完拼接回原链表
  • 不够 K 个的不反转

完整步骤

  • 创建虚拟头 dummy,方便接链表
  • 用 prev 记录每组的前一个节点(上一组的尾巴)
  • 检查够不够 K 个,不够就结束
  • 反转这 K 个节点
  • 把反转后的组拼接回去(拼前+拼后(记录后))
  • 移动 prev(更新上一组尾巴),继续下一组
java 复制代码
class Solution {
    public ListNode reverseKGroup(ListNode head, int k) {
        ListNode dummy=new ListNode(-1);
        dummy.next=head;//连接
        ListNode prev=dummy; //上一个的尾巴(初始化是虚拟节点)
        //一直下去
        while(true){
            //找到一组链表的头尾巴
            ListNode start=prev.next; //上一个的尾巴就是下一个的开始
            ListNode end=prev; 
            //找k个
            for(int i=0;i<k;i++){
                end=end.next;
                if(end==null) return dummy.next; //不足k组
            }
            //反转
            ListNode next=end.next; //记录下一组的开始

            ListNode cur=start,pre=null; 
            //结束条件:不再是原来的null,而是当前需要反转的节点不是下一组的开始
            while(cur!=next){
                ListNode ne=cur.next;//记录下一个需要反转的
                cur.next=pre; //指向前一个
                //下一个
                pre=cur;
                cur=ne;
            }
            //pre就是反转后的头 start就是反转后的尾
            prev.next=pre; //连接前面
            start.next=next;//连接后面

            prev=start;//给下一组记录上一组尾巴
        }
    }
}

分类2

203. 移除链表元素

1. 核心思路

链表删除节点的关键是找到「待删除节点的前驱节点」,但如果要删除的是头节点,没有前驱节点,处理起来会很麻烦。用虚拟头节点(dummy) 指向真实头节点,就能让「头节点」和「中间节点」的删除逻辑完全统一:

  1. 初始化虚拟头节点 dummy,让 dummy.next = head
  2. cur 指针从 dummy 开始遍历(始终指向当前节点的前驱);
  3. cur.next.val == val,则删除 cur.nextcur.next = curr.next.next);
  4. 否则,cur 后移;
  5. 最终返回 dummy.next(新的头节点)。

19. 删除链表的倒数第 N 个结点

  • 注意 先创建虚拟头节点 dummy,且 dummy.next = head。防止当链表头节点head为待删除节点时,删除该节点后链表头head为空的情况(边界情况)

    • 如果我们能得到倒数第n个节点的前驱节点而不是倒数第n个节点,那么删除操作会更加方便。因此我们可以考虑在初始时创建 快慢指针 fastslow,并将这两个指针指向哑节点 dummy,其余操作不变。这样一来,当 fast遍历到链表末尾时,slow的下一个节点就是我们需要删除的节点。
  • 快指针先走n步,然后快指针和慢指针再每次各走一步

  • 删除倒数第n个节点:slow.Next = slow.Next.Next,注意不是 slow.Next = fast

  • 最后返回虚拟头节点的后继节点:dummy.Next

java 复制代码
class Solution {
    public ListNode removeNthFromEnd(ListNode head, int n) {
        if(head==null) return null;
        ListNode dummy=new ListNode(-1);
        dummy.next=head;
        ListNode slow=dummy;
        ListNode fast=dummy;
        //快指针先走n步
        for(int i=0;i<n;i++) fast=fast.next;
        //再同时走(fast刚好走到尾,slow少走一个)
        while(fast.next!=null){
            fast=fast.next;
            slow=slow.next;
        }
        //slow就是中点的上个。删除
        slow.next=slow.next.next;
        return dummy.next; 
    }
}

计算长度的方法

java 复制代码
class Solution {
    public ListNode removeNthFromEnd(ListNode head, int n) {
        if(head==null) return null;
        int len=Len(head);
        ListNode dummy=new ListNode(-1);
        dummy.next=head;

        //总共是len个,倒数第n个,就是正向第len-n
        //所以找 len-n+1个,要删的前一个
        ListNode cur=dummy;
        for(int i=1;i<len-n+1;i++) cur=cur.next;

        cur.next=cur.next.next;


        return dummy.next; 
    }
    public int Len(ListNode node){
        int cnt=0;
        while(node!=null){
            node=node.next;
            cnt++;
        }
        return cnt;
    }
}

类似题目有:

分类3

21. 合并两个有序链表

两种方法:递归(不做演示)和 迭代 。

  • dummy 虚拟头结点 → 避免空指针
  • 循环条件l1 != null && l2 != null
  • 最后直接接剩余链表,不用再遍历
java 复制代码
class Solution {
    public ListNode mergeTwoLists(ListNode list1, ListNode list2) {
        //出现空链表
        if(list1==null) return list2;
        if(list2==null) return list1;

        ListNode l1=list1,l2=list2;
        ListNode dummy=new ListNode(-1);
        ListNode cur=dummy; //负责拼接
        //其中一个为空,就结束
        while(l1!=null&&l2!=null){
            int cnt1=l1.val;
            int cnt2=l2.val;
            //连接小的
            if(cnt1<cnt2){
                cur.next=l1;
                l1=l1.next;
            }else{
                cur.next=l2;
                l2=l2.next;
            }
            cur=cur.next; //下一个
        }
        //处理尾巴
        cur.next=l1==null?l2:l1;
        // if(l1==null) cur.next=l2;
        // if(l2==null) cur.next=l1;
        return dummy.next;
    }
}

23. 合并 K 个升序链表(小堆)

最简单暴力但最优的方法:

  1. 所有链表的头节点放进一个 ** 小根堆(优先队列)** 里
  2. 堆会自动把最小的节点放堆顶
  3. 每次取出最小的节点接到结果链表
  4. 取出后,如果这个节点后面还有节点,把它的 next 再放进堆里
  5. 重复到堆空为止

👉 一句话总结:用小根堆每次拿最小的拼接!

优点:

  • 时间复杂度低:O(N logK)(N 总节点数,K 链表数)
  • 代码短、逻辑简单
java 复制代码
class Solution {
    public ListNode mergeKLists(ListNode[] lists) {
        if(lists==null) return null;
        //升序,小堆
        PriorityQueue<ListNode> pq=new PriorityQueue<>((a,b)->a.val-b.val); 
        //插入每个链表头节点
        for(ListNode node:lists) {
            if(node!=null) pq.offer(node); //不为空才插入
        }

        ListNode dummy=new ListNode(-1);
        ListNode cur=dummy;
        //堆不为空,还有节点
        while(pq.size()>0){
            //获取堆顶,删掉
            ListNode node=pq.poll();
            cur.next=node;
            cur=cur.next;
            if(node.next!=null) //不为空才插入
                pq.add(node.next); //插入它的下一个元素

        }
        return dummy.next;
    }
}

分治思路

java 复制代码
class Solution {
    public ListNode mergeKLists(ListNode[] lists) {
        return mergeKLists(lists, 0, lists.length);
    }

    // 合并从 lists[i] 到 lists[j-1] 的链表
    private ListNode mergeKLists(ListNode[] lists, int i, int j) {
        int m = j - i;
        if (m == 0) {
            return null; // 注意输入的 lists 可能是空的
        }
        if (m == 1) {
            return lists[i]; // 无需合并,直接返回
        }
        ListNode left = mergeKLists(lists, i, i + m / 2); // 合并左半部分
        ListNode right = mergeKLists(lists, i + m / 2, j); // 合并右半部分
        return mergeTwoLists(left, right); // 最后把左半和右半合并
    }

    // 21. 合并两个有序链表
    private ListNode mergeTwoLists(ListNode list1, ListNode list2) {
        ListNode dummy = new ListNode(); // 用哨兵节点简化代码逻辑
        ListNode cur = dummy; // cur 指向新链表的末尾
        while (list1 != null && list2 != null) {
            if (list1.val < list2.val) {
                cur.next = list1; // 把 list1 加到新链表中
                list1 = list1.next;
            } else { // 注:相等的情况加哪个节点都是可以的
                cur.next = list2; // 把 list2 加到新链表中
                list2 = list2.next;
            }
            cur = cur.next;
        }
        cur.next = list1 != null ? list1 : list2; // 拼接剩余链表
        return dummy.next;
    }
}

148. 排序链表(中点分两份/合并)

排序链表(最优解法:归并排序

核心思路(3 步走)

找中点(快慢指针),快指针,先一步!

切两半 (断开链表),slow 停在左边尾

递归排序左右 → 合并两个有序链表

java 复制代码
class Solution {
    public ListNode sortList(ListNode head) {
        // (空 或 只有一个节点) 无需拆分
        if(head==null||head.next==null) return head;

        //找中点
        ListNode slow=head;
        ListNode fast=head.next;
        while(fast!=null&&fast.next!=null){
            fast=fast.next.next;
            slow=slow.next;
        }
        //断开
        ListNode head2=slow.next;
        slow.next=null;
        //递归排序分两半部分进行排序
        ListNode left=sortList(head);
        ListNode right=sortList(head2);
        //合并两个有序链表
        ListNode ret=mergeSortList(left,right);
        return ret;
    }
    public ListNode mergeSortList(ListNode l1,ListNode l2){
        ListNode dummy=new ListNode(-1);
        ListNode cur=dummy;
        while(l1!=null&&l2!=null){
            if(l1.val<l2.val){
                cur.next=l1;
                l1=l1.next;
            }else{
                cur.next=l2;
                l2=l2.next;
            }
            cur=cur.next;
        }
        //尾巴
        cur.next=l1==null?l2:l1;
        return dummy.next;
    }
}

分类4

141. 环形链表(判环)

判断快慢指针是否相遇(快指针两步,慢指针一步)

快指针走两步,慢指针走一步

  • 如果有环 :快慢指针一定会相遇
  • 如果没环 :快指针会走到 null

一句话:相遇 = 有环,不相遇 = 没环

java 复制代码
public class Solution {
    public boolean hasCycle(ListNode head) {
        ListNode slow=head;
        ListNode fast=head;
        //如果没环,一定可以走到终点
        while(fast!=null&&fast.next!=null){
            fast=fast.next.next;
            slow=slow.next;

            //如果有环,一定在里面相遇
            if(fast==slow) return true;
        }
        return false;
    }
}

142. 环形链表 II(找环入口)

方法一:快慢指针(最优 O (1) 空间,面试首选)

核心思路(死记结论)

  • 快指针走 2 步,慢指针走 1 步
  • 相遇 = 有环
  • 相遇后,慢指针回到头节点
  • 两个指针都每次走 1 步
  • 再次相遇的点 = 环的入口
java 复制代码
public class Solution {
    //快慢指针方法
    public ListNode detectCycle(ListNode head) {
        if(head==null) return null;
        ListNode slow=head;
        ListNode fast=head;
        while(fast!=null&&fast.next!=null){
            fast=fast.next.next; //走两步
            slow=slow.next;      //一步
            //相遇之后
            if(slow==fast){
                slow=head; //会头
                //快慢开始各一步
                while(slow!=fast){
                    slow=slow.next;
                    fast=fast.next;
                }
                return slow; //返回相遇点
            }
        }
        return null;
    }
}

疑惑:为什么第二次相遇就是入口?

java 复制代码
头节点 ----(a)----> 环入口 ----(b)----> 相遇点
                     ↓                 ↑
                     ---------(c)-------
  • a:头节点 → 环入口
  • b:环入口 → 第一次相遇点
  • c:第一次相遇点 → 环入口
  • 环总长 = b + c
  • 慢指针 slow **:每次走 1 步路程 =** a + b
  • 快指针 fast:每次走 2 步路程 = *a + b + n(b+c)**(多绕了 n 圈)

【起点到入口的距离】 = 【相遇点到入口的距离】

java 复制代码
2 × (a + b) = a + b + n × (b + c)
a + b = n × (b + c)
a + b = b + c   //当n等于1时
 a = c          //所以第二次:起点到环入口距离=相遇点到环入口距离

哈希表的方法

方法二:HashSet(最简单,好理解)

  • 遍历链表,把走过的节点都放进 HashSet
  • 如果某个节点已经在 set 里
  • → 这个节点就是环的入口
java 复制代码
public class Solution {
    //哈希表方法
    public ListNode detectCycle(ListNode head) {
        if(head==null) return null;
        ListNode cur=head;
        Set<ListNode> vis=new HashSet<>();

        while(cur!=null){
            //出现重复添加->入环点
            if(vis.contains(cur)) return cur;
            else vis.add(cur);//没有就添加
            cur=cur.next;
        }
        return null;
    }
}

160. 相交链表(找交点)

思路

  • cur1 从 A 走,cur2 从 B 走
  • 谁走到头,就换到另一条链表开头
  • 两人走的路程一样:A + 交 + B = B + 交 + A
  • 一定会在交点相遇
  • 不相交就一起走到 null
java 复制代码
public class Solution {
    public ListNode getIntersectionNode(ListNode headA, ListNode headB) {
        
        ListNode cur1=headA;
        ListNode cur2=headB;
        //不相等,就继续走
        //1.相遇 2.无交点走到尾巴,也相等null
        while(cur1!=cur2){
            //先同时走,走到尾,就从另外一个链表头开始走
            if(cur1==null) cur1=headB;
            else cur1=cur1.next;
            if(cur2==null) cur2=headA;
            else cur2=cur2.next;

        }
        //相遇就是交点 A+C+B=B+C+A
        return cur1;
    }
}
相关推荐
比尔盖茨的大脑2 小时前
为了学习 AI Agent,我做了一个 AI 阅读器(已开源)
前端·人工智能
1104.北光c°2 小时前
Leetcode3.无重复字符的最长子串 HashSet+HashMap 【hot100算法个人笔记】【java写法】
java·开发语言·笔记·程序人生·算法·leetcode·滑动窗口
星爷AG I2 小时前
16-3 归纳(AGI基础理论)
人工智能·agi
Binary-Jeff2 小时前
Maven 依赖作用域详解:compile、provided、runtime、test
java·spring·spring cloud·servlet·java-ee·maven
冬夜戏雪2 小时前
agent项目1:gemini-fullstack-langgraph-quickstart部署
人工智能
星爷AG I2 小时前
16-5 判断与决策(AGI基础理论)
人工智能·agi
QH_ShareHub2 小时前
Rstudio 与 R 打开 Rdata (压缩文件) 差异
java·前端·r语言
spencer_tseng2 小时前
apache-maven-3.9.6
java·maven
MicrosoftReactor2 小时前
技术速递|如何使用 GitHub Security Lab 的开源 AI 驱动框架进行漏洞扫描
人工智能·安全·开源·github·漏洞扫描