算法体系班1~12节 笔记整理
第1节
评估算法优劣的核心指标是什么
-
时间复杂度(流程决定)
-
额外空间复杂度(流程决定)
-
常数项时间(实现细节决定)
什么是时间复杂度?时间复杂度怎么估算?
常数时间的操作
确定算法流程的总操作数量与样本数量之间的表达式关系
只看表达式最高阶项的部分
何为常数时间的操作?
如果一个操作的执行时间不以具体样本量为转移,每次执行时间都是固定时间。称这样的操作为常数时间的操作。
常见的常数时间的操作
•常见的算术运算(+、-、*、/、% 等)
•常见的位运算(>>、>>>、<<、|、&、^等)
•赋值、比较、自增、自减操作等
•数组寻址操作
总之,执行时间固定的操作都是常数时间的操作。
反之,执行时间不固定的操作,都不是常数时间的操作。
算法和数据结构学习的大脉络
-
知道怎么算的算法
-
知道怎么试的算法
我们所有的题目讲解,对于大脉络的实践贯穿始终
认识对数器
你在网上找到了某个公司的面试题,你想了好久,感觉自己会做,但是你找不到在线测试,你好心烦..
你和朋友交流面试题,你想了好久,感觉自己会做,但是你找不到在线测试,你好心烦..
你在网上做笔试,但是前几个测试用例都过了,突然一个巨大无比数据量来了,结果你的代码报错了,如此大的数据量根本看不出哪错了,你好心烦…
认识二分法
经常见到的类型是在一个有序数组上,开展二分搜索
但有序真的是所有问题求解时使用二分的必要条件吗?
不只要能正确构建左右两侧的淘汰逻辑,你就可以二分。
- 在一个有序数组中,找某个数是否存在
- 在一个有序数组中,找>=某个数最左侧的位置
- 在一个有序数组中,找<=某个数最右侧的位置
- 局部最小值问题
总结
- 对数器,起到的作用类似 TDD
第二节
认识异或运算
异或运算的性质
-
0^N == N N^N == 0
-
异或运算满足交换律和结合率
上面的两个性质用无进位相加来理解就非常的容易
题目一:
如何不用额外变量交换两个数
题目二:
一个数组中有一种数出现了奇数次,其他数都出现了偶数次,怎么找到并打印这种数
题目三:
怎么把一个int类型的数,提取出最右侧的1来
题目四:
一个数组中有两种数出现了奇数次,其他数都出现了偶数次,怎么找到并打印这两种数
题目五:
一个数组中有一种数出现K次,其他数都出现了M次,
M > 1, K < M
找到,出现了K次的数,
要求,额外空间复杂度O(1),时间复杂度O(N)
第三节
单向链表
单向链表节点结构(可以实现成范型)
public class Node { public int value; public Node next; public Node(int data) { value = data; } }
双向链表
双向链表节点结构 public class DoubleNode { public int value; public DoubleNode last; public DoubleNode next; public DoubleNode(int data) { value = data; } }
单向链表和双向链表最简单的练习
链表相关的问题几乎都是coding问题
-
单链表和双链表如何反转
-
把给定值都删除
这里就是熟悉结构。链表还有哪些常见面试题,后续有专门一节来系统学习。
栈和队列
逻辑概念
栈:数据先进后出,犹如弹匣
队列:数据先进先出,好似排队
栈和队列的实际实现
双向链表实现
数组实现
既然语言都有这些结构和api,为什么还需要手撸练习?
- 算法问题无关语言
- 语言提供的api是有限的,当有新的功能是api不提供的,就需要改写
- 任何软件工具的底层都是最基本的算法和数据结构,这是绕不过去的
栈和队列的常见面试题
怎么用数组实现不超过固定大小的队列和栈?
栈:正常使用
队列:环形数组
栈和队列的常见面试题
题目一
实现一个特殊的栈,在基本功能的基础上,再实现返回栈中最小元素的功能
- pop、push、getMin操作的时间复杂度都是 O(1)。
- 设计的栈类型可以使用现成的栈结构。
题目二
- 如何用栈结构实现队列结构
- 如何用队列结构实现栈结构
这两种结构的应用实在是太多了,在刷题时我们会大量见到
递归?这东西是什么啊?
求数组arr[L..R]中的最大值,怎么用递归方法实现。
- 将[L..R]范围分成左右两半。左:[L..Mid] 右[Mid+1..R]
- 左部分求最大值,右部分求最大值
- [L..R]范围上的最大值,是max
注意:2)是个递归过程,当范围上只有一个数,就可以不用再递归了
对于新手来说,把调用的过程画出结构图是必须的,这有利于分析递归
递归并不是玄学,递归底层是利用系统栈来实现的
任何递归函数都一定可以改成非递归
Master公式
形如
T(N) = a * T(N/b) + O(N^d)(其中的a、b、d都是常数)
的递归函数,可以直接通过Master公式来确定时间复杂度
如果 log(b,a) < d,复杂度为O(N^d)
如果 log(b,a) > d,复杂度为O(N^log(b,a))
如果 log(b,a) == d,复杂度为O(N^d * logN)
哈希表
- 哈希表在使用层面上可以理解为一种集合结构
- 如果只有key,没有伴随数据value,可以使用HashSet结构
- 如果既有key,又有伴随数据value,可以使用HashMap结构
- 有无伴随数据,是HashMap和HashSet唯一的区别,实际结构是一回事
- 使用哈希表增(put)、删(remove)、改(put)和查(get)的操作,可以认为时间复杂度为 O(1),但是常数时间比较大
- 放入哈希表的东西,如果是基础类型,内部按值传递,内存占用是这个东西的大小
- 放入哈希表的东西,如果不是基础类型,内部按引用传递,内存占用是8字节
有序表
-
有序表在使用层面上可以理解为一种集合结构
-
如果只有key,没有伴随数据value,可以使用TreeSet结构
-
如果既有key,又有伴随数据value,可以使用TreeMap结构
-
有无伴随数据,是TreeSet和TreeMap唯一的区别,底层的实际结构是一回事
-
有序表把key按照顺序组织起来,而哈希表完全不组织
-
红黑树、AVL树、size-balance-tree和跳表等都属于有序表结构,只是底层具体实现不同
-
放入如果是基础类型,内部按值传递,内存占用就是这个东西的大小
-
放入如果不是基础类型,内部按引用传递,内存占用是8字节
-
不管是什么底层具体实现,只要是有序表,都有以下固定的基本功能和固定的时间复杂度
有序表常用方法
1)void put(K key, V value)
将一个(key,value)记录加入到表中,或者将key的记录 更新成value。
2)V get(K key)
根据给定的key,查询value并返回。
3)void remove(K key)
移除key的记录。
4)boolean containsKey(K key)
询问是否有关于key的记录。
5)K firstKey()
返回所有键值的排序结果中,最小的那个。
6)K lastKey()
返回所有键值的排序结果中,最大的那个。
7)K floorKey(K key)
返回<= key 离key最近的那个
8)K ceilingKey(K key)
返回>= key 离key最近的那个
哈希表和有序表的原理
以后讲!现在的你可能会听不懂,只需要记住:
哈希表在使用时,增删改查时间复杂度都是O(1)
有序表在使用时,比哈希表功能多,时间复杂度都是O(logN)
第四节
归并排序
- 整体是递归,左边排好序+右边排好序+merge让整体有序
- 让其整体有序的过程里用了排外序方法
- 利用master公式来求解时间复杂度
- 当然可以用非递归实现
归并排序复杂度
T(N) = 2*T(N/2) + O(N^1)
根据master可知时间复度为O(N*logN)
merge过程需要辅助数组,所以额外空间复杂度为O(N)
归并排序的实质是把比较行为变成了有序信息并传递,比O(N^2)的排序快
常见面试题一
在一个数组中,一个数左边比它小的数的总和,叫数的小和,所有数的小和累加起来,叫数组小和。求数组小和。
例子: [1,3,4,2,5]
1左边比1小的数:没有
3左边比3小的数:1
4左边比4小的数:1、3
2左边比2小的数:1
5左边比5小的数:1、3、4、 2
所以数组的小和为1+1+3+1+1+3+4+2=16
常见面试题2
在一个数组中,
任何一个前面的数a,和任何一个后面的数b,
如果(a,b)是降序的,就称为逆序对
返回数组中所有的逆序对
常见面试题3
在一个数组中,
对于每个数num,求有多少个后面的数 * 2 依然<num,求总个数
比如:[3,1,7,0,2]
3的后面有:1,0
1的后面有:0
7的后面有:0,2
0的后面没有
2的后面没有
所以总共有5个
第五节
常见面试题4
题目描述:
https://leetcode.com/problems/count-of-range-sum/
给定一个数组arr,两个整数lower和upper,
返回arr中有多少个子数组的累加和在[lower,upper]范围上
快速排序
Partition过程
给定一个数组arr,和一个整数num。请把小于等于num的数放在数组的左边,大于num的数放在数组的右边。
要求额外空间复杂度O(1),时间复杂度O(N)
荷兰国旗问题
给定一个数组arr,和一个整数num。请把小于num的数放在数组的左边,等于num的数放在中间,大于num的数放在数组的右边。
要求额外空间复杂度O(1),时间复杂度O(N)
快速排序1.0
在arr[L..R]范围上,进行快速排序的过程:
- 用arr[R]对该范围做partition,<= arr[R]的数在左部分并且保证arr[R]最后来到左部分的最后一个位置,记为M; <= arr[R]的数在右部分(arr[M+1..R])
- 对arr[L..M-1]进行快速排序(递归)
- 对arr[M+1..R]进行快速排序(递归)
因为每一次partition都会搞定一个数的位置且不会再变动,所以排序能完成
快速排序2.0
在arr[L..R]范围上,进行快速排序的过程:
- 用arr[R]对该范围做partition,< arr[R]的数在左部分,== arr[R]的数中间,>arr[R]的数在右部分。假设== arr[R]的数所在范围是[a,b]
- 对arr[L..a-1]进行快速排序(递归)
- 对arr[b+1..R]进行快速排序(递归)
因为每一次partition都会搞定一批数的位置且不会再变动,所以排序能完成
快速排序1.0和2.0的时间复杂度分析
数组已经有序的时候就是复杂度最高的时候
时间复杂度O(N^2)
快速排序3.0(随机快排+荷兰国旗技巧优化)
在arr[L..R]范围上,进行快速排序的过程:
- 在这个范围上,随机选一个数记为num,
- 用num对该范围做partition,< num的数在左部分,== num的数中间,>num的数在右部分。假设== num的数所在范围是[a,b]
- 对arr[L..a-1]进行快速排序(递归)对arr[b+1..R]进行快速排序(递归)
因为每一次partition都会搞定一批数的位置且不会再变动,所以排序能完成
随机快排的时间复杂度分析
- 通过分析知道,划分值越靠近中间,性能越好;越靠近两边,性能越差
- 随机选一个数进行划分的目的就是让好情况和差情况都变成概率事件
- 把每一种情况都列出来,会有每种情况下的时间复杂度,但概率都是1/N
- 那么所有情况都考虑,时间复杂度就是这种概率模型下的长期期望!
时间复杂度O(N*logN),额外空间复杂度O(logN)都是这么来的。
要求掌握递归和非递归两种方式写出随机快排
第六节
比较器
- 比较器的实质就是重载比较运算符
- 比较器可以很好的应用在特殊标准的排序上
- 比较器可以很好的应用在根据特殊标准排序的结构上
- 写代码变得异常容易,还用于范型编程
任何比较器的统一约定
即如下方法:
•@Override
•public int compare(T o1, T o2) ;
返回负数的情况,就是o1比o2优先的情况
返回正数的情况,就是o2比o1优先的情况
返回0的情况,就是o1与o2同样优先的情况
堆结构
- 堆结构就是用数组实现的完全二叉树结构
- 完全二叉树中如果每棵子树的最大值都在顶部就是大根堆
- 完全二叉树中如果每棵子树的最小值都在顶部就是小根堆
- 堆结构的heapInsert与heapify操作
- 堆结构的增大和减少
- 优先级队列结构,就是堆结构
语言提供的堆结构vs手写的堆结构
取决于,你有没有动态改信息的需求!
语言提供的堆结构,如果你动态改数据,不保证依然有序
手写堆结构,因为增加了对象的位置表,所以能够满足动态改信息的需求
堆排序
- 先让整个数组都变成大根堆结构,建立堆的过程:
- 从上到下的方法,时间复杂度为O(N*logN)
- 从下到上的方法,时间复杂度为O(N)
-
把堆的最大值和堆末尾的值交换,然后减少堆的大小之后,再去调整堆,一直周而复始,时间复杂度为O(N*logN)
-
堆的大小减小成0之后,排序完成
与堆有关的题目
已知一个几乎有序的数组。几乎有序是指,如果把数组排好顺序的话,每个元素移动的距离一定不超过k,并且k相对于数组长度来说是比较小的。
请选择一个合适的排序策略,对这个数组进行排序。
第七节
最大线段重合问题(用堆的实现)
给定很多线段,每个线段都有两个数[start, end],
表示线段开始位置和结束位置,左右都是闭区间
规定:
- 线段的开始和结束位置一定都是整数值
- 线段重合区域的长度必须>=1
返回线段最多重合区域中,包含了几条线段
手动改写堆(非常重要)!
系统提供的堆无法做到的事情:
- 已经入堆的元素,如果参与排序的指标方法变化,
系统提供的堆无法做到时间复杂度O(logN)调整!都是O(N)的调整!
- 系统提供的堆只能弹出堆顶,做不到自由删除任何一个堆中的元素,
或者说,无法在时间复杂度O(logN)内完成!一定会高于O(logN)
根本原因:无反向索引表
手动改写堆的代码讲解
- 建立反向索引表
- 建立比较器
- 核心在于各种结构相互配合,非常容易出错
手动改写堆题目
练习一:
给定一个整型数组,int[] arr;和一个布尔类型数组,boolean[] op
两个数组一定等长,假设长度为N,arr[i]表示客户编号,op[i]表示客户操作
arr = [ 3 , 3 , 1 , 2, 1, 2, 5…
op = [ T , T, T, T, F, T, F…
依次表示:3用户购买了一件商品,3用户购买了一件商品,1用户购买了一件商品,2用户购买了一件商品,1用户退货了一件商品,2用户购买了一件商品,5用户退货了一件商品…
练习一:
一对arr[i]和op[i]就代表一个事件:
用户号为arr[i],op[i] == T就代表这个用户购买了一件商品
op[i] == F就代表这个用户退货了一件商品
现在你作为电商平台负责人,你想在每一个事件到来的时候,
都给购买次数最多的前K名用户颁奖。
所以每个事件发生后,你都需要一个得奖名单(得奖区)。
练习二:
得奖系统的规则:
- 如果某个用户购买商品数为0,但是又发生了退货事件,
则认为该事件无效,得奖名单和上一个事件发生后一致,例子中的5用户
-
某用户发生购买商品事件,购买商品数+1,发生退货事件,购买商品数-1
-
每次都是最多K个用户得奖,K也为传入的参数
如果根据全部规则,得奖人数确实不够K个,那就以不够的情况输出结果
- 得奖系统分为得奖区和候选区,任何用户只要购买数>0,
一定在这两个区域中的一个
- 购买数最大的前K名用户进入得奖区,
在最初时如果得奖区没有到达K个用户,那么新来的用户直接进入得奖区
-
如果购买数不足以进入得奖区的用户,进入候选区
-
如果候选区购买数最多的用户,已经足以进入得奖区,
该用户就会替换得奖区中购买数最少的用户(大于才能替换),
如果得奖区中购买数最少的用户有多个,就替换最早进入得奖区的用户
如果候选区中购买数最多的用户有多个,机会会给最早进入候选区的用户
- 候选区和得奖区是两套时间,
因用户只会在其中一个区域,所以只会有一个区域的时间,另一个没有
从得奖区出来进入候选区的用户,得奖区时间删除,
进入候选区的时间就是当前事件的时间(可以理解为arr[i]和op[i]中的i)
从候选区出来进入得奖区的用户,候选区时间删除,
进入得奖区的时间就是当前事件的时间(可以理解为arr[i]和op[i]中的i)
- 如果某用户购买数==0,不管在哪个区域都离开,区域时间删除,
离开是指彻底离开,哪个区域也不会找到该用户
如果下次该用户又发生购买行为,产生>0的购买数,
会再次根据之前规则回到某个区域中,进入区域的时间重记
练习三:
请遍历arr数组和op数组,遍历每一步输出一个得奖名单
public List<List
第八节
前缀数和
- 单个字符串中,字符从前到后的加到一棵多叉树上
- 字符放在路上,节点上有专属的数据项(常见的是pass和end值)
- 所有样本都这样添加,如果没有路就新建,如有路就复用
- 沿途节点的pass值增加1,每个字符串结束时来到的节点end值增加1
可以完成前缀相关的查询
例子
设计一种结构。用户可以:
- void insert(String str) 添加某个字符串,可以重复添加,每次算1个
- int search(String str) 查询某个字符串在结构中还有几个
- void delete(String str) 删掉某个字符串,可以重复删除,每次算1个
- int prefixNumber(String str) 查询有多少个字符串,是以str做前缀的
前缀树路的实现方式
- 固定数组实现
- 哈希表实现
Ps:我们实际来一把,对数器帮你找到bug的展示
不基于比较的排序
桶排序思想下的排序:计数排序 & 基数排序
-
桶排序思想下的排序都是不基于比较的排序
-
时间复杂度为O(N),额外空间负载度O(M)
-
应用范围有限,需要样本的数据状况满足桶的划分
计数排序和基数排序
-
一般来讲,计数排序要求,样本是整数,且范围比较窄
-
一般来讲,基数排序要求,样本是10进制的正整数
一旦要求稍有升级,改写代价增加是显而易见的
排序算法的稳定性
稳定性是指同样大小的样本再排序之后不会改变相对次序
对基础类型来说,稳定性毫无意义
对非基础类型来说,稳定性有重要意义
有些排序算法可以实现成稳定的,而有些排序算法无论如何都实现不成稳定的
第九节
排序算法时间复杂度总结
时间复杂度 额外空间复杂度 稳定性
选择排序 O(N^2) O(1) 无
冒泡排序 O(N^2) O(1) 有
插入排序 O(N^2) O(1) 有
归并排序 O(N*logN) O(N) 有
随机快排 O(N*logN) O(logN) 无
堆排序 O(N*logN) O(1) 无
========================================================
计数排序 O(N) O(M) 有
基数排序 O(N) O(N) 有
排序算法总结
-
不基于比较的排序,对样本数据有严格要求,不易改写
-
基于比较的排序,只要规定好两个样本怎么比大小就可以直接复用
-
基于比较的排序,时间复杂度的极限是O(N*logN)
-
时间复杂度O(N*logN)、额外空间复杂度低于O(N)、且稳定的基于比较的排序是不存在的。
-
为了绝对的速度选快排、为了省空间选堆排、为了稳定性选归并
常见的坑
- 归并排序的额外空间复杂度可以变成O(1),“归并排序 内部缓存法”,但是将变得不再稳定。
- “原地归并排序" 是垃圾贴,会让时间复杂度变成O(N^2)
- 快速排序稳定性改进,“01 stable sort”,但是会对样本数据要求更多。
工程上对排序的改进
-
稳定性的考虑
-
充分利用O(N*logN)和O(N^2)排序各自的优势
链表问题
面试时链表解题的方法论
-
对于笔试,不用太在乎空间复杂度,一切为了时间复杂度
-
对于面试,时间复杂度依然放在第一位,但是一定要找到空间最省的方法
链表面试题常用数据结构和技巧
-
使用容器(哈希表、数组等)
-
快慢指针
快慢指针
-
输入链表头节点,奇数长度返回中点,偶数长度返回上中点
-
输入链表头节点,奇数长度返回中点,偶数长度返回下中点
-
输入链表头节点,奇数长度返回中点前一个,偶数长度返回上中点前一个
-
输入链表头节点,奇数长度返回中点前一个,偶数长度返回下中点前一个
常见面试题
题目一
给定一个单链表的头节点head,请判断该链表是否为回文结构。
-
哈希表方法特别简单(笔试用)
-
改原链表的方法就需要注意边界了(面试用)
题目二
将单向链表按某值划分成左边小、中间相等、右边大的形式
-
把链表放入数组里,在数组上做partition(笔试用)
-
分成小、中、大三部分,再把各个部分之间串起来(面试用)
题目三
一种特殊的单链表节点类描述如下
class Node { int value; Node next; Node rand; Node(int val) { value = val; } }
rand指针是单链表节点结构中新增的指针,rand可能指向链表中的任意一个节点,也可能指向null。
给定一个由Node节点类型组成的无环单链表的头节点 head,请实现一个函数完成这个链表的复制,并返回复制的新链表的头节点。
【要求】
时间复杂度O(N),额外空间复杂度O(1)
第十节
题目四
给定两个可能有环也可能无环的单链表,头节点head1和head2。请实现一个函数,如果两个链表相交,请返回相交的 第一个节点。如果不相交,返回null
【要求】
如果两个链表长度之和为N,时间复杂度请达到O(N),额外空间复杂度 请达到O(1)。
题目五
能不能不给单链表的头节点,只给想要删除的节点,就能做到在链表上把这个点删掉?
二叉树
结构描述:
Class Node { V value; Node left; Node right; }
二叉树的先序、中序、后序遍历
先序:任何子树的处理顺序都是,先头节点、再左子树、然后右子树
中序:任何子树的处理顺序都是,先左子树、再头节点、然后右子树
后序:任何子树的处理顺序都是,先左子树、再右子树、然后头节点
递归方式实现二叉树的先序、中序、后序遍历
- 理解递归序
- 先序、中序、后序都可以在递归序的基础上加工出来
- 第一次到达一个节点就打印就是先序、第二次打印即中序、第三次即后序
X祖先节点 交集
非递归方式实现二叉树的先序、中序、后序遍历
- 任何递归函数都可以改成非递归
- 自己设计压栈的来实现
第十一节
实现二叉树的按层遍历
-
其实就是宽度优先遍历,用队列
-
可以通过设置flag变量的方式,来发现某一层的结束(看题目)
实现二叉树的序列化和反序列化
- 先序方式序列化和反序列化
- 按层方式序列化和反序列化
题目
题目一
Leetcode
431. Encode N-****ary Tree to Binary Tree
题目二
如何设计一个打印整棵树的打印函数
题目三
求二叉树最宽的层有多少个节点
题目四
二叉树结构如下定义:
Class Node { V value; Node left; Node right; Node parent; }
给你二叉树中的某个节点,返回该节点的后继节点
题目五
请把一段纸条竖着放在桌子上,然后从纸条的下边向上方对折1次,压出折痕后展开。此时折痕是凹下去的,即折痕突起的方向指向纸条的背面。 如果从纸条的下边向上方连续对折2次,压出折痕后展开,此时有三条折痕,从上到下依次是下折痕、下折痕和上折痕。
给定一个输入参数N,代表纸条都从下边向上方连续对折N次。 请从上到下打印所有折痕的方向。
例如:N=1时,打印: down N=2时,打印: down down up
第十二节
题目六
判断二叉树是否是完全二叉树
会在后面的题目中用二叉树的递归套路来解这个题
二叉树的递归套路
可以解决面试中绝大多数的二叉树问题尤其是树型dp问题
本质是利用递归遍历二叉树的便利性
- 假设以X节点为头,假设可以向X左树和X右树要任何信息
- 在上一步的假设下,讨论以X为头节点的树,得到答案的可能性(最重要)
- 列出所有可能性后,确定到底需要向左树和右树要什么样的信息
- 把左树信息和右树信息求全集,就是任何一棵子树都需要返回的信息S
- 递归函数都返回S,每一棵子树都这么要求
- 写代码,在代码中考虑如何把左树的信息和右树信息整合出整棵树的信息
题目
题目一
判断二叉树是否是搜索二叉树
题目二
给定一棵二叉树的头节点head,返回这颗二叉树是不是平衡二叉树
题目三
给定一棵二叉树的头节点head,返回这颗二叉树是不是满二叉树
题目四
给定一棵二叉树的头节点head,返回这颗二叉树中最大的二叉搜索子树的大小
题目五
给定一棵二叉树的头节点head,任何两个节点之间都存在距离,返回整棵二叉树的最大距离
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 开发者必知的日志记录最佳实践
· SQL Server 2025 AI相关能力初探
· Linux系列:如何用 C#调用 C方法造成内存泄露
· AI与.NET技术实操系列(二):开始使用ML.NET
· 记一次.NET内存居高不下排查解决与启示
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· 开源Multi-agent AI智能体框架aevatar.ai,欢迎大家贡献代码
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· AI技术革命,工作效率10个最佳AI工具