阅读视图
机器人下鱼塘、救膝盖?这场机器人“脱口秀”路子太野了
鱼塘机器人?迷你皮卡?膝盖外骨骼?
看惯了这一年里,人形机器人只有“进工厂、进家庭、无所不能”这一种宏大叙事;也听腻了各种研讨会上,大家西装革履地端着,一脸严肃地分析“行业卡点”在哪里。
不得不说,相比之下,笔者最近去的一场机器人脱口秀,画风实在太清新了。
在这里,你看到有人为了解决怎么在鱼塘里搞自动化,直接把机器人“开”进了水里,搞出了水产养殖机器人; 在这里,有人根本不在乎什么工业革命,纯粹是因为自己去青藏高原徒步“膝盖疼”,就硬磕出了一套重装徒步外骨骼; 甚至还有人直言不讳,说自己做AI健身教练起家,核心技术竟然是“怎么靠忽悠,骗用户多流点汗”。
这一幕幕,就发生在11月21日下午,地瓜机器人主办的“地心引力”派对。
没有冗长的开场白,十位背景迥异的开发者轮番登台——他们之中,有刚拿融资的连续创业者,有大厂转身的技术高管,有还在象牙塔里造梦的00后学生,也有深耕十年的行业老炮。大家抛开客套,只聊自己手里那个“正在生长”的机器人。
看惯了正襟危坐的研讨会,这些看似“离谱”、实则“生猛”的故事,让我们看到了中国具身智能最真实的样子。
当机器人“入侵”鱼塘与荒野
机器人到底离改变我们的生活有多远?
在各种高大上的研讨会上,大家总有各种各样的担忧:现在的机器人是不是太脆弱了?还需要多少年才能走出实验室?但在地瓜机器人的这场派对中,最迷人的地方在于,大家的共识惊人的一致:不需要等未来,机器人现在就能改变生活。
机器人被一群胆大包天的开发者扔进了泥泞、荒野,甚至是你周末的露营地里,去解决那些真实得有点千奇百怪的痛点。从一个很简单的初心,到一个真正的产品成型,机器人也可以是这样的。
机器人,可以是为了解决父辈的心酸。
来自塘前燕机器人的创始人韩冬,上台没讲算法,先讲了自己的身份——一个“鱼塘娃”。
当大多数人还在卷人形机器人如何优雅地走进家庭时,韩冬想用自己的能力,解决从小看到父辈们经历的辛酸。他发现,水产养殖虽然产值巨大,却是自动化的“绝对洼地”。
“养鱼就像赌博。”韩冬在现场说道。鱼塘是一个极其复杂的生物系统:天气的变化、甚至一只鸟飞过留下的粪便,都可能带来病毒,引发系统性崩盘。
于是,他决定做一个鱼塘机器人。他把一位做自动驾驶的朋友、拉下了水,两个人从研究千万级的自动驾驶订单,变成了天天盯着鱼塘发呆。

为了在这个从没有自动化的领域做出智能机器人,他们花费无数的心思去从零开始积累数据。一代鱼塘娃,长大了,可以用机器人去守住父辈们最不确定的收成了。
机器人,也可以是纯粹因为膝盖疼而长出来的执念。
无待动力的创始人毛榉一上台,画风瞬间变成了一场荒野求生电影。
他分享的起点不是什么宏大的商业计划书,而是大三休学去青藏高原的一次重装徒步。那是一次绝望的旅程:原本计划骑马穿越祁连山,结果因为荒野里前后十几公里都没有草,马儿“罢工”了。于是,浪漫的骑马穿越变成了残酷的“人背马”重装徒步。
在那几天的绝望中,他切身体会到了真正的痛点——徒步时上下坡膝盖承受的巨大冲击,那种痛感让人根本无心欣赏眼前的美景。“市面上的外骨骼为什么没用?因为它们大多是针对髋关节的,只能帮你在平地上把腿抬起来。下坡要减速,要保持,所以你的肌肉一直在撕裂……这都是膝关节主导的。”
为了对抗物理意义上的“地心引力”,为了让自己和驴友们在看风景时不再因为身体痛苦而分心,他硬是磕出了一款不做髋关节、只死磕膝盖痛点的外骨骼。这不是为了风口而造的产品,这是从骨头缝的痛感里长出来的真实需求。
机器人,还可以用来温柔地修补这个世界的缺憾。
如果说前两者是在解决物理世界的难题,睿亚极光的Steve,则是在用最硬核的技术做最温柔的事——把NASA的技术用在盲人身上。
Steve的师承背景非常“硬”——他的导师来自NASA JPL(喷气推进实验室),研究的是太空级别的MEMS与AI融合技术。但Steve没有选择去做航天,而是把这种原本服务于火星探测的“高维技术”,带回了地面,甚至带到了最边缘的群体——盲人身边。
“在太空里算力有限,所以传感器必须极致高效;盲人的世界也是一样。”Steve展示的为盲人做了一个最方便的感知设备。

在内蒙古,有机构利用他们的设备,训练出了专门识别路边“电线杆”的模型,帮助盲人避开这些危险的“隐形杀手”。他的技术被微软CEO纳德拉点赞,现在也在服务于残特奥会的盲人运动员。Steve用行动证明了一种极客的浪漫:最顶级的技术,不一定非要去征服星辰大海,也可以低下头,去照亮一部分人脚下的路。
机器人到底该往何处去?从泥泞的鱼塘,到缺氧的高原,再到盲人脚下的路,这些开发者没有被“高大上”的概念束缚。在这场派对上,笔者看到一群开发者用自己的作品在说:只要有痛点的地方,就是机器人野蛮生长的土壤。
新老“造物者”的疯狂对撞
另一个让笔者惊讶的是,在这场派对上,“造物者”的画像变得极其丰富。具身智能不再是象牙塔里的概念,它正在笼络各种各样的人——有深沉的行业老兵,有狡黠的连续创业者,也有还在象牙塔里的00后学生。
网境智能的万琪和State Labs的宋军就是行业老兵的典型代表。在做具身智能之前,这两位创始人都在各自的领域(云计算、AR/VR等)有着极深的积累。
转战具身智能后,他们不约而同地盯上了落地最前沿、也最棘手的问题——大脑(模型)和本体(硬件)之间该如何连接?
万琪盯上了机器人的那只“手”。
他运用之前在空间视觉领域的积累,做了一个自带高频空间感知和轨迹规划能力的智能夹爪(Gripper)。

在传统的具身智能领域,机器人想要知道手在哪里,往往要靠昂贵的、刚性极强的本体(六轴+关节)来一层层死算末端位置。这不仅需要大量算力,对硬件的一致性要求极高。而万琪直接做出了一个自带“独立小脑”的夹爪——不需要管胳膊怎么动,这只手自己能看、自己能算、自己能精准定位。
这一刀,精准切掉了行业里两个“最不性感但最要命”的成本痛点:
- 一是售前成本: 以前为了保证机械臂的精度,出厂前要跑三个月的疲劳测试;现在末端能自己定位,这三个月的时间直接省了。
- 二是售后成本: 机器人跑久了关节会漂移、螺丝会松动。以前这就得让工程师上门修,现在根本不需要——哪怕本体松了个螺丝,这只聪明的“手”也能自己修正误差,把活儿干了。
这极大降低了人形机器人和机械臂的交付门槛。
而宋军则盯上了“模型不懂硬件”的鸿沟。
有着底层计算架构背景的他,正在解决分布式计算的难题。现在的通用大模型很强,但它们不懂具体的硬件参数。宋军试图让大模型用“100条数据”,就能在一小时内学会控制一个新的硬件。
这两位老兵,就像是生态里的“修路人”。当外界盯着模型怎么冲向SOTA(最前沿)时,他们默默低头,把落地的“最后一公里”修平了。
而在他们身后,还有一群默默“点火”的人。
创客火的严国陶就是其中的典型。作为深圳创客文化的见证者,他从最早混迹于柴火创客空间,到如今深耕无人机教育,干了整整12年。

他做的事,是把高冷的工业级无人机技术,拆解成了孩子们能上手*“空中乐高”。依托地瓜机器人的算力底座,他让学生们不再只是玩那个会飞的塑料玩具,而是能真正接触到边缘计算、视觉识别和集群编队这些硬核技术。
默默传递火种的,还有有方机器人的王明松、有你同创的佘鹏飞,这几位教育领域的创业者,仿佛中国机器人沃土的“播种者”,确保未来十年,中国依然有源源不断的年轻人愿意跳进这个行业。
同在这个行业努力的,还有转战具身智能的连续创业者。
从上一家公司做健身AI“骗汗水”,到如今做陪伴机器人“骗感情”,Roy一直在思考一个问题:交互与人性的极致在哪里? 他试图给冷冰冰的机器人注入一种“狡黠”的温情,让具身智能不仅能干活,还能懂你。
这种对体验的极致追求,让他与传统的“工具型”开发者划清了界限。在他眼中,真正C端的产品不靠冷冰冰的参数堆砌,而靠细腻的情感共鸣——他要造的不是一台机器,而是一个能让用户产生依赖、甚至产生生命幻觉的新物种。
但最让人眼前一亮的,还是以朗极智能Peter为代表的“00后新势力”。
Peter和他的联合创始人们的故事,简直就是这一代年轻造物者的典型缩影。他们的起点纯粹是因为“好玩”——高中时为了酷,在学校里造了一辆能自平衡的自行车;大一时为了展示极客范儿,把自动驾驶算法塞进轮椅,造了一个纯视觉导航的智能轮椅。
那时候,这只是一个少年的天马行空,是单纯的“炫技”。
直到他们开始将简单的demo真正产品化,添加真正厚重的AI技术作为支撑,让它真正长成一个AI产品。
从智能轮椅长出了智能代步车,又长出了可以骑乘的户外mini皮卡——一辆长得像露营车,却有200公斤载重的小车;它没有方向盘,却能通过视觉像忠犬一样自动跟随主人,甚至提供主动助力。
从“智能轮椅”的Demo到“迷你皮卡”的商品,朗极智能解决的是露营圈的真实痛点。在Kickstarter上甚至还没发货,就众筹到了几十万美金。
从“玩票”到“量产”,为什么野性创意没有死在实验室?
Peter的故事很热血,但现实往往很冷酷。
像朗极智能这样从校园走出来的公司,在行业里其实是幸存者。对于绝大多数创业者来说,从学校实验室里那个让导师点头的“原型机”,到真正能够量产、能经受住市场毒打的“商品”,中间隔着一道深不见底的“死亡谷”。
对于一个学生团队,对于一个学生团队来说,算力成本、工程化难度、供应链资源……每一项都能让项目中途夭折。
为什么这些年轻的野性创意没有死在实验室里,而是真的长成了商业公司?
答案不在Peter一个人身上。或许我们能从这场机器人派对的主办方找到端倪——你会发现,所有登台的“幸存者”,脚下都踩着一块早已平整好的“土壤”——地瓜机器人的生态。
为什么Peter这样的00后,或者像韩冬这样跨界的“鱼塘娃”,能如此快速地拿出产品?
这与地瓜机器人近年来带来的技术上的“平权”有关。
得益于地瓜统一的软硬件地基。从RDK系列开发板到TogetheROS.Bot操作系统,地瓜把最难啃的底层硬骨头(芯片适配、感知算法、通信中间件)都替开发者啃完了。
这就好比是给开发者提供了一套通用的底盘。创业者不再需要从零去造轮子、去写最底层的驱动,他们只需要专注于自己最擅长的——去定义场景,去发挥那些天马行空的创意。地瓜机器人搭建的地基,正在让造机器人的门槛正在变得越来越低。
光有技术还不够,对于初创公司,尤其是像朗极智能这样的学生团队,最缺的往往不是代码,而是让公司活下去的“粮草”。
这就不得不提地瓜生态里的另一股核心力量——“地心引力计划”(DGP)。
在现场,我们看到了这个计划实打实的“含金量”。它不是虚头巴脑的口号,而是针对初创企业最痛的穴位精准施针:
- 缺钱? 提供专属的产品折扣(20%以上),直接降低硬件BOM成本,让学生也造得起;
- 缺人? 帮你连接500+院校的人才库,解决招人难;
- 缺资源? 对接100+产业链伙伴和资本,甚至开放海外全经销渠道,帮你出海。

尤其对于像朗极智能这样从校园走出来的团队,最隐蔽的坑往往在“选型”上。
在实验室里做Demo,和真实世界的产品量产完全不同。一旦要变成量产的商品,每一块钱的BOM(物料)成本都决定了生与死。是用性能强悍的X5,还是性价比更高的X3?是用哪种传感器才能在户外稳定工作?这些问题,教科书里没写,却是量产路上的“鬼门关”。
这时候,“地心引力计划”提供的一对一技术支持和选型服务就显出了极高的含金量。手把手地帮你挑出那个最优解,是初创团队最稀缺、也最难在市场上买到的服务。
正是这种全方位的托举,让“地心引力”成为了对抗“创业阻力”的关键。它让孤独的徒步者找到了队友,让象牙塔里的学生找到了供应链,让技术老兵找到了落地场景。
走出那个热气腾腾的派对现场,笔者不禁在想:中国具身智能的未来,到底应该是什么样?
它不应该只有几只高耸入云、万众瞩目的“独角兽”,孤零零地立在荒原上。
它更需要像地瓜机器人这样肥沃的“土壤”,以及“地心引力”这样持续不断的能量供给。
只有底座稳了,生态才能像热带雨林一样:既有参天大树,也有像Kago这样好玩的灌木,更有像鱼塘机器人、外骨骼这样扎根泥土的野草。它们千奇百怪,它们野蛮生长,它们共同构成了一个生机勃勃的中国机器人江湖。
明宇制药有限公司-B向港交所提交上市申请书
新莱福:新莱福资管计划拟减持公司不超87.43万股股份
美联储理事沃勒称他主张在12月降息
热门中概股美股盘前普涨,哔哩哔哩涨超6%
美股大型科技股盘前普涨,谷歌涨超3%
沙特投资银行通过公开拍卖售出8.01亿里亚尔土地资产
TS 项目升级 React 18 到 19 的一些事情
广电计量:拟8亿元投建广电计量西南(成都)检测基地
React - 【useEffect 与 useLayoutEffect】 区别 及 使用场景
Qt 6 实战:C++ 调用 QML 回调方法(异步场景完整实现)
kotlin-5
太棒了!现在让我们进入 Kotlin 更深入的高级特性和实际应用场景。这部分将涵盖泛型、注解、反射、DSL等企业级开发必备技能。
Kotlin 高级特性:企业级开发实战
三十二、泛型深入:型变、星投影与 reified
1. 型变(Variance)
// 不变(Invariant)- 默认行为
class Box<T>(val value: T)
fun main() {
val stringBox = Box("Hello")
// val anyBox: Box<Any> = stringBox // 错误!Box<String> 不是 Box<Any> 的子类型
}
// 协变(Covariant)- 使用 out 关键字
class ReadOnlyBox<out T>(val value: T) {
fun get(): T = value
// fun set(newValue: T) {} // 错误!不能有消费 T 的方法
}
// 逆变(Contravariant)- 使用 in 关键字
class WriteOnlyBox<in T> {
fun set(value: T) {
println("设置值: $value")
}
// fun get(): T // 错误!不能有生产 T 的方法
}
fun testVariance() {
// 协变示例
val stringBox: ReadOnlyBox<String> = ReadOnlyBox("Text")
val anyBox: ReadOnlyBox<Any> = stringBox // 正确!因为 out 关键字
// 逆变示例
val anyWriteBox: WriteOnlyBox<Any> = WriteOnlyBox()
val stringWriteBox: WriteOnlyBox<String> = anyWriteBox // 正确!因为 in 关键字
}
2. 星投影(Star Projection)
fun printSize(list: List<*>) {
println("列表大小: ${list.size}")
// println(list[0]) // 不能安全地访问元素内容
}
fun <T> compareFirstSecond(list: List<T>, comparator: Comparator<in T>): Boolean
where T : Comparable<T> {
return if (list.size >= 2) {
comparator.compare(list[0], list[1]) > 0
} else false
}
// 使用 reified 关键字实现类型检查
inline fun <reified T> checkType(value: Any): Boolean {
return value is T
}
fun main() {
val list = listOf(1, 2, 3)
printSize(list)
println(checkType<String>("Hello")) // true
println(checkType<Int>("Hello")) // false
}
三十三、注解与反射
1. 自定义注解
// 定义注解
@Target(AnnotationTarget.CLASS, AnnotationTarget.FUNCTION)
@Retention(AnnotationRetention.RUNTIME)
annotation class ApiVersion(val version: String)
@Target(AnnotationTarget.PROPERTY)
annotation class JsonExclude
@Target(AnnotationTarget.PROPERTY)
annotation class JsonName(val name: String)
// 使用注解
@ApiVersion("1.0")
data class User(
@JsonName("user_name")
val name: String,
val age: Int,
@JsonExclude
val password: String
)
// 注解处理器模拟
fun processAnnotations(obj: Any) {
val klass = obj::class
val apiVersion = klass.annotations.find { it is ApiVersion } as? ApiVersion
apiVersion?.let { println("API 版本: ${it.version}") }
klass.memberProperties.forEach { prop ->
prop.annotations.forEach { annotation ->
when (annotation) {
is JsonName -> println("属性 ${prop.name} 将被序列化为 ${annotation.name}")
is JsonExclude -> println("属性 ${prop.name} 将被排除")
}
}
}
}
2. 反射实战
import kotlin.reflect.full.*
import kotlin.reflect.jvm.isAccessible
class SecretClass {
private val secretValue = "机密信息"
private fun secretMethod() = "机密方法"
public fun publicMethod() = "公开方法"
}
fun reflectExample() {
val instance = SecretClass()
val klass = instance::class
println("=== 类信息 ===")
println("类名: ${klass.simpleName}")
println("成员属性: ${klass.memberProperties.map { it.name }}")
println("成员函数: ${klass.memberFunctions.map { it.name }}")
println("\n=== 访问私有成员 ===")
// 访问私有属性
val secretProperty = klass.memberProperties.find { it.name == "secretValue" }
secretProperty?.let {
it.isAccessible = true
println("私有属性值: ${it.get(instance)}")
}
// 调用私有方法
val secretMethod = klass.memberFunctions.find { it.name == "secretMethod" }
secretMethod?.let {
it.isAccessible = true
println("私有方法结果: ${it.call(instance)}")
}
}
三十四、类型安全的构建器(DSL)
1. HTML DSL 构建器
class HtmlElement(val name: String, val content: String = "") {
private val children = mutableListOf<HtmlElement>()
private val attributes = mutableMapOf<String, String>()
fun attribute(name: String, value: String) {
attributes[name] = value
}
fun child(element: HtmlElement) {
children.add(element)
}
override fun toString(): String {
val attrString = if (attributes.isNotEmpty()) {
" " + attributes.entries.joinToString(" ") { "${it.key}="${it.value}"" }
} else ""
return if (children.isEmpty()) {
"<$name$attrString>$content</$name>"
} else {
"<$name$attrString>${children.joinToString("")}$content</$name>"
}
}
}
// DSL 构建函数
fun html(block: HtmlBuilder.() -> Unit): HtmlElement {
val builder = HtmlBuilder("html")
builder.block()
return builder.build()
}
class HtmlBuilder(private val rootName: String) {
private val root = HtmlElement(rootName)
private var currentElement: HtmlElement = root
fun head(block: HeadBuilder.() -> Unit) {
val headBuilder = HeadBuilder()
headBuilder.block()
root.child(headBuilder.build())
}
fun body(block: BodyBuilder.() -> Unit) {
val bodyBuilder = BodyBuilder()
bodyBuilder.block()
root.child(bodyBuilder.build())
}
fun build(): HtmlElement = root
}
class HeadBuilder {
private val head = HtmlElement("head")
fun title(text: String) {
head.child(HtmlElement("title", text))
}
fun build(): HtmlElement = head
}
class BodyBuilder {
private val body = HtmlElement("body")
fun h1(text: String, block: H1Builder.() -> Unit = {}) {
val h1Builder = H1Builder(text)
h1Builder.block()
body.child(h1Builder.build())
}
fun p(text: String) {
body.child(HtmlElement("p", text))
}
fun build(): HtmlElement = body
}
class H1Builder(private val text: String) {
private val h1 = HtmlElement("h1", text)
fun style(css: String) {
h1.attribute("style", css)
}
fun build(): HtmlElement = h1
}
// 使用 DSL
fun createHtmlPage() {
val page = html {
head {
title("我的网页")
}
body {
h1("欢迎来到 Kotlin DSL") {
style("color: blue;")
}
p("这是一个使用 DSL 构建的 HTML 页面")
}
}
println(page)
}
2. 数据库查询 DSL
// 查询 DSL 定义
class QueryBuilder {
private var table: String = ""
private val conditions = mutableListOf<String>()
private var limit: Int? = null
private var orderBy: String? = null
fun from(table: String) {
this.table = table
}
fun where(condition: String) {
conditions.add(condition)
}
fun limit(count: Int) {
this.limit = count
}
fun orderBy(column: String) {
this.orderBy = column
}
fun build(): String {
val query = StringBuilder("SELECT * FROM $table")
if (conditions.isNotEmpty()) {
query.append(" WHERE ").append(conditions.joinToString(" AND "))
}
orderBy?.let {
query.append(" ORDER BY $it")
}
limit?.let {
query.append(" LIMIT $it")
}
return query.toString()
}
}
// DSL 入口函数
fun query(block: QueryBuilder.() -> Unit): String {
val builder = QueryBuilder()
builder.block()
return builder.build()
}
// 使用 DSL 构建查询
fun buildQueries() {
val simpleQuery = query {
from("users")
}
println("简单查询: $simpleQuery")
val complexQuery = query {
from("orders")
where("status = 'completed'")
where("amount > 100")
orderBy("created_at DESC")
limit(10)
}
println("复杂查询: $complexQuery")
}
三十五、合约(Contracts)与内联优化
import kotlin.contracts.*
// 使用合约优化智能转换
@OptIn(ExperimentalContracts::class)
fun String?.isNotNullOrEmpty(): Boolean {
contract {
returns(true) implies (this@isNotNullOrEmpty != null)
}
return this != null && this.isNotEmpty()
}
fun processText(text: String?) {
if (text.isNotNullOrEmpty()) {
// 由于合约,编译器知道 text 不为 null
println(text.length) // 不需要安全调用
println(text.uppercase())
}
}
// 内联类 - 包装类型而不产生运行时开销
@JvmInline
value class Password(private val value: String) {
init {
require(value.length >= 8) { "密码必须至少8个字符" }
}
fun mask(): String = "*".repeat(value.length)
}
@JvmInline
value class UserId(val value: Int)
fun login(userId: UserId, password: Password) {
println("用户 ${userId.value} 登录,密码: ${password.mask()}")
}
三十六、多平台项目(KMP)基础
// 公共模块 - 共享业务逻辑
expect class Platform() {
val platform: String
}
class Greeting {
private val platform = Platform()
fun greet(): String {
return "Hello from ${platform.platform}"
}
}
// Android 实现
// androidMain/Platform.kt
actual class Platform actual constructor() {
actual val platform: String = "Android"
}
// iOS 实现
// iosMain/Platform.kt
actual class Platform actual constructor() {
actual val platform: String = "iOS"
}
// 共享业务逻辑
class Calculator {
fun add(a: Int, b: Int): Int = a + b
fun multiply(a: Int, b: Int): Int = a * b
fun calculateExpression(expression: String): Int {
return when {
expression.contains("+") -> {
val parts = expression.split("+")
add(parts[0].trim().toInt(), parts[1].trim().toInt())
}
expression.contains("*") -> {
val parts = expression.split("*")
multiply(parts[0].trim().toInt(), parts[1].trim().toInt())
}
else -> throw IllegalArgumentException("不支持的表达式")
}
}
}
三十七、性能优化与最佳实践
1. 集合操作优化
fun collectionOptimization() {
val numbers = (1..1000000).toList()
// 不好的写法:多次中间操作
val badResult = numbers
.filter { it % 2 == 0 }
.map { it * 2 }
.filter { it > 1000 }
.take(10)
// 好的写法:使用序列(惰性求值)
val goodResult = numbers.asSequence()
.filter { it % 2 == 0 }
.map { it * 2 }
.filter { it > 1000 }
.take(10)
.toList()
println("结果: $goodResult")
}
// 使用 groupBy 优化复杂操作
data class Person(val name: String, val age: Int, val city: String)
fun optimizeGrouping() {
val people = listOf(
Person("Alice", 25, "Beijing"),
Person("Bob", 30, "Shanghai"),
Person("Charlie", 25, "Beijing"),
Person("Diana", 30, "Shanghai")
)
// 按城市分组,然后按年龄分组
val grouped = people.groupBy({ it.city }, { it.age })
println("分组结果: $grouped")
// 统计每个城市的平均年龄
val averageAges = people.groupingBy { it.city }
.fold(0.0) { accumulator, element ->
accumulator + element.age
}.mapValues { it.value / people.count { p -> p.city == it.key } }
println("平均年龄: $averageAges")
}
2. 内存优化模式
// 使用对象池避免重复创建
class ConnectionPool private constructor() {
private val available = mutableListOf<Connection>()
private val inUse = mutableSetOf<Connection>()
fun getConnection(): Connection {
return available.removeFirstOrNull()?.also { inUse.add(it) }
?: Connection().also { inUse.add(it) }
}
fun releaseConnection(connection: Connection) {
inUse.remove(connection)
available.add(connection)
}
companion object {
val instance by lazy { ConnectionPool() }
}
}
class Connection {
fun connect() = println("连接建立")
fun disconnect() = println("连接关闭")
}
// 使用 use 函数自动管理资源
fun readFileSafely() {
// 模拟资源管理
val resource = object : AutoCloseable {
override fun close() = println("资源已释放")
fun read() = "文件内容"
}
val content = resource.use {
it.read()
}
println("内容: $content") // 资源会自动关闭
}
三十八、完整实战项目:构建事件总线系统
import kotlinx.coroutines.*
import kotlinx.coroutines.channels.*
import kotlinx.coroutines.flow.*
import kotlin.reflect.KClass
// 事件基类
sealed class Event {
data class UserLoggedIn(val userId: String) : Event()
data class OrderCreated(val orderId: String, val amount: Double) : Event()
data class PaymentProcessed(val paymentId: String, val success: Boolean) : Event()
object SystemShutdown : Event()
}
// 事件处理器接口
interface EventHandler<T : Event> {
suspend fun handle(event: T)
}
// 事件总线
object EventBus {
private val handlers = mutableMapOf<KClass<out Event>, MutableList<EventHandler<*>>>()
private val eventChannel = Channel<Event>(Channel.UNLIMITED)
// 注册事件处理器
fun <T : Event> registerHandler(eventClass: KClass<T>, handler: EventHandler<T>) {
handlers.getOrPut(eventClass) { mutableListOf() }.add(handler as EventHandler<*>)
}
// 发布事件
fun publish(event: Event) {
eventChannel.trySend(event)
}
// 启动事件处理循环
fun start() = GlobalScope.launch {
for (event in eventChannel) {
processEvent(event)
}
}
private suspend fun processEvent(event: Event) {
val eventClass = event::class
val eventHandlers = handlers[eventClass] ?: return
eventHandlers.forEach { handler ->
try {
@Suppress("UNCHECKED_CAST")
(handler as EventHandler<Event>).handle(event)
} catch (e: Exception) {
println("事件处理失败: ${e.message}")
}
}
}
}
// 具体的事件处理器
class UserActivityLogger : EventHandler<Event.UserLoggedIn> {
override suspend fun handle(event: Event.UserLoggedIn) {
delay(100) // 模拟处理时间
println("📝 用户登录日志: ${event.userId} 在 ${System.currentTimeMillis()} 登录")
}
}
class OrderProcessor : EventHandler<Event.OrderCreated> {
override suspend fun handle(event: Event.OrderCreated) {
delay(200)
println("💰 订单处理: 订单 ${event.orderId} 金额 ${event.amount}")
}
}
class PaymentNotifier : EventHandler<Event.PaymentProcessed> {
override suspend fun handle(event: Event.PaymentProcessed) {
delay(150)
val status = if (event.success) "成功" else "失败"
println("📧 支付通知: 支付 ${event.paymentId} $status")
}
}
class SystemMonitor : EventHandler<Event.SystemShutdown> {
override suspend fun handle(event: Event.SystemShutdown) {
println("🛑 系统关闭: 开始清理资源...")
delay(500)
println("🛑 系统关闭: 资源清理完成")
}
}
// 使用事件总线
fun main() = runBlocking {
// 注册处理器
EventBus.registerHandler(Event.UserLoggedIn::class, UserActivityLogger())
EventBus.registerHandler(Event.OrderCreated::class, OrderProcessor())
EventBus.registerHandler(Event.PaymentProcessed::class, PaymentNotifier())
EventBus.registerHandler(Event.SystemShutdown::class, SystemMonitor())
// 启动事件总线
EventBus.start()
// 模拟发布事件
EventBus.publish(Event.UserLoggedIn("user123"))
EventBus.publish(Event.OrderCreated("order456", 99.99))
EventBus.publish(Event.PaymentProcessed("pay789", true))
EventBus.publish(Event.OrderCreated("order999", 49.99))
EventBus.publish(Event.PaymentProcessed("pay000", false))
delay(1000) // 等待事件处理完成
EventBus.publish(Event.SystemShutdown())
delay(1000)
println("程序结束")
}
下一步学习方向
你现在已经掌握了 Kotlin 的企业级开发技能!接下来可以深入:
- Kotlin 编译器插件开发:自定义编译期处理
- Kotlin 元编程:在编译时生成代码
- Kotlin 与 Java 互操作高级技巧:类型映射、异常处理
- Kotlin 服务端开发:使用 Ktor、Spring Boot
- Kotlin 前端开发:使用 Compose for Web
- Kotlin 移动端开发:Compose Multiplatform
这些高级特性将让你在 Kotlin 开发中游刃有余,能够构建复杂、高性能的企业级应用!
🗣️面试官: 那些常见的前端面试场景问题
1. 页面白屏如何排查?
第一步:快速分类(30秒) "页面白屏主要有五种原因:JavaScript执行错误、资源加载失败、CSS样式问题、接口异常和浏览器兼容性。其中JavaScript错误最常见,特别是SPA应用中的未捕获异常。"
第二步:排查方法(1分钟) "我的排查步骤是:首先查看Console面板的错误信息,这能快速定位JS异常;然后检查Network面板确认资源加载状态;接着用Elements面板验证DOM和样式;移动端问题会用vConsole或真机调试。生产环境结合Sentry等监控系统分析。"
第三步:预防措施(30秒) "预防方面建立错误边界、资源容错机制、统一接口异常处理、兼容性检测,同时搭建监控告警体系。"
一、基础检测流程
1. 控制台检查(Console)
// 主动捕获全局错误(放在入口文件最前面)
window.addEventListener('error', function(event) {
console.error('全局捕获:', event.error);
// 可上报到监控系统
});
// 检查console是否有以下类型错误:
// - SyntaxError (语法错误)
// - TypeError (类型错误)
// - ReferenceError (引用错误)
// - 404资源加载失败
2. 网络请求检查(Network)
-
关键指标:
- HTML文档状态码(200/304/404/500)
- JS/CSS资源加载状态
- 接口请求是否阻塞渲染
-
检测示例:
// 检查关键资源是否加载完成
const resourceCheck = () => {
const entries = performance.getEntriesByType('resource');
const criticalResources = entries.filter(entry =>
entry.initiatorType === 'script' ||
entry.initiatorType === 'css'
);
criticalResources.forEach(res => {
if(res.responseStatus >= 400) {
console.error(`资源加载失败: ${res.name}`, res);
}
});
};
window.addEventListener('load', resourceCheck);
二、深度检测方法
1. DOM渲染检测
// 检测DOM树是否正常构建
function checkDOMReady() {
return new Promise((resolve) => {
const check = () => {
if(document.body && document.body.children.length > 0) {
resolve(true);
} else {
setTimeout(check, 50);
}
};
check();
});
}
// 使用示例
checkDOMReady().then((isReady) => {
if(!isReady) {
console.error('DOM渲染超时');
// 上报白屏信息
}
});
2. 框架特定检测
Vue应用检测:
// 在main.js中添加
new Vue({
render: h => h(App),
errorCaptured(err, vm, info) {
console.error('Vue组件错误:', err, info);
// 可上报错误
return false; // 阻止错误继续向上传播
}
}).$mount('#app');
// 检查根组件挂载
if(!document.querySelector('#app').__vue__) {
console.error('Vue根实例挂载失败');
}
React应用检测:
// Error Boundary组件
class ErrorBoundary extends React.Component {
componentDidCatch(error, info) {
console.error('React组件错误:', error, info);
// 上报错误
}
render() {
return this.props.children;
}
}
// 检查React根组件
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
<ErrorBoundary>
<App />
</ErrorBoundary>
);
三、性能相关检测
1. 长任务检测
// 检测阻塞渲染的长时间任务
const observer = new PerformanceObserver((list) => {
for(const entry of list.getEntries()) {
if(entry.duration > 50) { // 超过50ms的任务
console.warn('长任务影响渲染:', entry);
}
}
});
observer.observe({entryTypes: ["longtask"]});
2. 关键渲染路径监控
// 使用Performance API监控关键时间点
const perfData = window.performance.timing;
const metrics = {
domReady: perfData.domComplete - perfData.domLoading,
loadTime: perfData.loadEventEnd - perfData.navigationStart
};
if(metrics.domReady > 3000) {
console.error('DOM解析时间过长:', metrics);
}
四、自动化检测方案
1. Puppeteer检测脚本
const puppeteer = require('puppeteer');
(async () => {
const browser = await puppeteer.launch();
const page = await browser.newPage();
// 监听控制台错误
page.on('console', msg => {
if(msg.type() === 'error') {
console.log('页面错误:', msg.text());
}
});
// 设置超时检测
await Promise.race([
page.goto('https://your-site.com'),
new Promise((_, reject) =>
setTimeout(() => reject(new Error('页面加载超时')), 5000)
)
]);
// 检查可见内容
const content = await page.evaluate(() => {
return {
bodyText: document.body.innerText,
childCount: document.body.children.length
};
});
if(content.childCount === 0 || content.bodyText.length < 10) {
console.error('检测到白屏现象');
}
await browser.close();
})();
2. 真实用户监控(RUM)
// 使用浏览器的MutationObserver监控DOM变化
const observer = new MutationObserver((mutations) => {
if(!document.querySelector('#app')?.innerHTML) {
// 上报白屏事件
beacon.send('white-screen', {
url: location.href,
ua: navigator.userAgent
});
}
});
observer.observe(document.body, {
childList: true,
subtree: true
});
五、常见白屏场景示例
-
资源加载失败:
<!-- 错误的资源路径 --> <script src="/wrong-path/app.js"></script> -
语法错误:
// 缺少括号导致整个脚本不执行 function test() { console.log('hello' } -
框架初始化失败:
// Vue示例 - 挂载元素不存在 new Vue({el: '#not-exist'}); -
CSS阻塞:
<!-- 不正确的CSS引入阻塞渲染 --> <link rel="stylesheet" href="nonexist.css"> -
第三方库冲突:
// 两个库都修改了Array原型 libraryA.modifyPrototype(); libraryB.modifyPrototype(); // 冲突导致错误 ```
2.前端埋点
一、页面生命周期埋点
1. 页面加载阶段
// 记录页面开始加载时间
const pageStartTime = Date.now();
// 监听页面加载完成
window.addEventListener('load', () => {
const loadTime = Date.now() - pageStartTime;
track('page_load', {
load_time: loadTime,
referrer: document.referrer,
resource_status: checkResources()
});
});
// 检查关键资源加载状态
function checkResources() {
return performance.getEntriesByType('resource').map(res => ({
name: res.name,
type: res.initiatorType,
duration: res.duration.toFixed(2)
}));
}
2. 用户交互阶段
// 点击事件埋点(支持事件委托)
document.body.addEventListener('click', (e) => {
const target = e.target.closest('[data-track]');
if(target) {
track('element_click', {
element_id: target.id,
track_type: target.dataset.track,
position: `${e.clientX},${e.clientY}`
});
}
});
// 滚动深度记录
let maxScroll = 0;
window.addEventListener('scroll', _.throttle(() => {
const currentScroll = window.scrollY / document.body.scrollHeight;
if(currentScroll > maxScroll) {
maxScroll = currentScroll;
track('scroll_depth', { depth: Math.round(maxScroll * 100) });
}
}, 1000));
3. 页面停留时长计算
let activeStart = Date.now();
let inactiveTime = 0;
// 用户活跃状态检测
document.addEventListener('mousemove', resetActiveTimer);
document.addEventListener('keydown', resetActiveTimer);
function resetActiveTimer() {
if(inactiveTime > 0) {
track('user_inactive', { duration: inactiveTime });
inactiveTime = 0;
}
activeStart = Date.now();
}
// 每10秒检测一次活跃状态
setInterval(() => {
if(Date.now() - activeStart > 15000) { // 15秒无操作视为不活跃
inactiveTime += 10000;
} else {
track('user_active', { duration: 10000 });
}
}, 10000);
二、特殊场景处理
1. 页面隐藏/显示
// 页面可见性变化监听
document.addEventListener('visibilitychange', () => {
if(document.hidden) {
track('page_hide', {
stay_time: Date.now() - pageStartTime,
scroll_depth: maxScroll
});
} else {
track('page_show');
}
});
2. 页面关闭前上报
// 确保页面关闭前数据上报
window.addEventListener('beforeunload', () => {
const totalStay = Date.now() - pageStartTime;
navigator.sendBeacon('/api/track', JSON.stringify({
event: 'page_close',
active_time: totalStay - inactiveTime,
scroll_depth: maxScroll
}));
});
三、数据上报优化方案
1. 批量上报机制
let eventQueue = [];
const MAX_QUEUE = 5;
const FLUSH_INTERVAL = 3000;
function addToQueue(event) {
eventQueue.push(event);
if(eventQueue.length >= MAX_QUEUE) {
flushQueue();
}
}
function flushQueue() {
if(eventQueue.length === 0) return;
const batchData = { batch: eventQueue };
navigator.sendBeacon('/api/batch', JSON.stringify(batchData));
eventQueue = [];
}
// 定时刷新队列
setInterval(flushQueue, FLUSH_INTERVAL);
2. 关键指标计算
// 计算FMP(首次有效绘制)
new PerformanceObserver((entryList) => {
const [entry] = entryList.getEntriesByName('first-contentful-paint');
track('fmp', { value: entry.startTime.toFixed(2) });
}).observe({type: 'paint', buffered: true});
// 计算LCP(最大内容绘制)
new PerformanceObserver((entryList) => {
const entries = entryList.getEntries();
const lastEntry = entries[entries.length - 1];
track('lcp', { value: lastEntry.startTime.toFixed(2) });
}).observe({type: 'largest-contentful-paint', buffered: true});
四、面试回答精简版
"我们实现全链路埋点主要分三个阶段:
-
加载阶段:
- 用
performance API采集DNS/TTFB等指标 - 监听
load事件记录完整加载时间 - 检查关键资源状态(如图片/脚本)
- 用
-
交互阶段:
- 事件委托监听全局点击(带
data-track属性) - 节流处理滚动事件计算最大深度
- 通过
mousemove/keydown检测活跃状态
- 事件委托监听全局点击(带
-
离开阶段:
-
visibilitychange处理页面切换 -
beforeunload+sendBeacon确保关闭前上报 - 计算总停留时长和有效活跃时间
-
3.那为什么大家都使用请求 GIF 图片的方式上报埋点数据呢?
-
防止跨域问题:前端监控的请求常常会遇到跨域问题,这可能会影响监控的准确性和可用性。然而,图片的src属性并不会跨域,因此使用GIF图片作为埋点可以正常发起请求,从而有效避免跨域问题。
-
防止阻塞页面加载:在创建资源节点后,通常只有当对象注入到浏览器的DOM树后,浏览器才会实际发送资源请求。但反复操作DOM会引发性能问题,且载入js/css资源会阻塞页面渲染,从而影响用户体验。与此不同,构造图片打点不需要插入DOM,只要在js中new出Image对象就能发起请求,这样就不会有阻塞问题。即使在没有js的浏览器环境中,也能通过img标签正常打点,这是其他类型的资源请求所做不到的。
-
体积小,节约流量:相比其他图片格式(如BMP和PNG),GIF图片具有更小的体积。例如,最小的BMP文件需要74个字节,PNG需要67个字节,而合法的GIF只需要43个字节。因此,使用GIF作为埋点可以显著节约流量,提高数据传输效率。
-
浏览器支持性好:所有浏览器都支持Image对象,即使不支持XMLHttpRequest对象也一样。这意味着使用GIF进行埋点上报可以在各种浏览器环境中稳定运行。
-
记录错误的过程很少出错:某些情况下,如Ajax通信过程中页面跳转,请求可能会被取消。但使用图片进行埋点上报则不会遇到这个问题,特别是在记录离开页面打点行为的时候会很有用。
120行代码,实现丝滑滚动的时间轴组件
前言
产品又看见一个非常高级的时间轴页面交互(对于前端来说,实现不难,主要是花时间),我们尝试一下让Trae 的solo来实现,最简洁的代码实现,尽可能的描述一下细节,这样才可以最准确的实现,少走弯路,不浪费我们宝贵的时间(有这时间多看几篇技术文章,不香?啊哈哈哈)
首先先搭建好一个项目,这里使用的是vite+vue3+ts,然后是对应的安装tailwind css,这些都是让Trae solo模式帮我们搭建一下,我们就不用从0搭建了
先来看看最终的效果实现,你很难相信这是一次对话就实现的
一.首先是描述细节
1.时间轴的中间是可以滚动的,然后要根据鼠标进行滚动,不要滚动条
2.奇数是在上面,时间轴的连接线比较长,偶数是在下面,连接线比较短
3.箭头在右下角固定,保证用户知道这个是时间轴
4.卡片最大宽度是240px,超出隐藏
5.线和卡片中间使用倒三角连接起来
附加上一张ui设计的截图,就可以开始提问了
等待了一会,就实现了
来看看代码,119行就实现了,看起来确实优雅,解读一下代码trae solo是如何实现这个组件的
1.卡片(奇偶项交错,下移以视觉区分),使用tailwind的mt-[210px],
<div
class="relative max-w-[230px] overflow-hidden rounded-lg bg-[#1D2129] shadow-lg"
:class="i % 2 !== 0 ? 'mt-[210px]' : ''"
>
<!-- 卡片头部:深色背景 -->
<div class="flex items-center justify-between px-3 py-2">
<span class="text-sm font-medium text-white">共7条</span>
<span class="cursor-pointer text-xs text-white">全部></span>
</div>
<!-- 卡片内容:两张图片并排 -->
<div class="flex gap-2 overflow-hidden p-3">
<div class="relative shrink-0 overflow-hidden rounded">
<img :src="item.image" class="aspect-video h-[150px] w-[89px] object-cover" alt="">
</div>
<div class="relative shrink-0 overflow-hidden rounded">
<img :src="item.image" class="aspect-video h-[150px] w-[89px] object-cover" alt="">
</div>
<div class="relative shrink-0 overflow-hidden rounded">
<img :src="item.image" class="aspect-video h-[150px] w-[89px] object-cover" alt="">
</div>
</div>
</div>
2.底部小三角形指示器,有没有勾起你当时学前端的回忆,那道经典的面试题的,你是怎么实现一个三角形的
<div class="border-x-[12px] border-t-[12px] border-[#1D2129] border-x-transparent" />
3.竖线:奇数项加长(视觉第1、3、5项),使用tailwind的h-[310px]和h-[101px]来实现连接线的长短
<div
class="w-[2px]" :class="[ isCreation ? 'bg-blue-500' : 'bg-[#685FE1]', i % 2 === 0 ? 'h-[310px]' : 'h-[101px]', ]"
/>
4.底部圆点的实现,也是使用一个div来实现的
<div
class="z-10 size-4 rounded-full border-[3px] bg-white" :class="[ isCreation ? 'border-blue-500' : 'border-[#685FE1]', ]"
/>
5.底部横向主线(黑色)贯穿整个容器
<div class="absolute inset-x-0 bottom-[35px] h-[2px] bg-black"/>
6.底部时间轴右侧箭头(固定在轴线右端,靠内边距 right-[-11px] 可调)
<div class="pointer-events-none absolute bottom-[56px] right-[-11px] z-[999]">
<!-- 向右的箭头 SVG,样式可改 -->
<svg class="size-6 text-black" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg" aria-hidden="true">
<path d="M5 12h13" stroke="currentColor" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round" />
<path d="M13 5l7 7-7 7" stroke="currentColor" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round" />
</svg>
</div>
关键代码,通过监听鼠标滚轮来实现横向滚动的
onMounted(() => {
const el = scrollRef.value
if (!el)
return
// 鼠标滚轮横向滚动
el.addEventListener('wheel', (e) => {
if (Math.abs(e.deltaY) > Math.abs(e.deltaX)) {
e.preventDefault()
el.scrollLeft += e.deltaY
}
})
})
来看看代码行数,确实很惊艳,简单的119行就实现了
总结
trae的solo确实比之前好很多,也惊艳到我了,前端可真是越来越难了,以后的时间排期也会进一步的压缩了,还是要适当的使用ai来辅助我们的编程,把一些比较容易实现的,耗时间的工作交给ai去实现,我们专注于一些工程化和架构上面去,也可以适当学一些node项目,提高我们自己的核心竞争力,加油吧,前端工程师们。
《Flutter全栈开发实战指南:从零到高级》- 17 -核心动画
简单题,简单做(Python/Java/C++/C/Go/JS/Rust)
题意:计算 $\textit{nums}$ 每个前缀的二进制数值 $x$,判断 $x$ 是否为 $5$ 的倍数。
比如 $\textit{nums}=[1,1,0,1]$,每个前缀对应的二进制数分别为 $1,11,110,1101$。
如何计算这些二进制数呢?
在十进制中,我们往 $12$ 的右边添加 $3$,得到 $123$,做法是 $12\cdot 10 + 3 = 123$。
对于二进制,做法类似,往 $110$ 的右边添加 $1$,得到 $1101$,做法是 $110\cdot 2 + 1 = 1101$,或者 $110\ \texttt{<<}\ 1\ |\ 1 = 1101$。
注意本题 $\textit{nums}$ 很长,算出的二进制数 $x$ 很大,但我们只需要判断 $x\bmod 5=0$ 是否成立。可以在中途取模,也就是每次循环计算出新的 $x$ 后,把 $x$ 替换成 $x\bmod 5$。为什么可以在中途取模?原理见 模运算的世界:当加减乘除遇上取模。
###py
class Solution:
def prefixesDivBy5(self, nums: List[int]) -> List[bool]:
ans = [False] * len(nums)
x = 0
for i, bit in enumerate(nums):
x = (x << 1 | bit) % 5
ans[i] = x == 0
return ans
###java
class Solution {
public List<Boolean> prefixesDivBy5(int[] nums) {
List<Boolean> ans = new ArrayList<>(nums.length); // 预分配空间
int x = 0;
for (int bit : nums) {
x = (x << 1 | bit) % 5;
ans.add(x == 0);
}
return ans;
}
}
###cpp
class Solution {
public:
vector<bool> prefixesDivBy5(vector<int>& nums) {
vector<bool> ans(nums.size());
int x = 0;
for (int i = 0; i < nums.size(); i++) {
x = (x << 1 | nums[i]) % 5;
ans[i] = x == 0;
}
return ans;
}
};
###c
bool* prefixesDivBy5(int* nums, int numsSize, int* returnSize) {
*returnSize = numsSize;
bool* ans = malloc(numsSize * sizeof(bool));
int x = 0;
for (int i = 0; i < numsSize; i++) {
x = (x << 1 | nums[i]) % 5;
ans[i] = x == 0;
}
return ans;
}
###go
func prefixesDivBy5(nums []int) []bool {
ans := make([]bool, len(nums))
x := 0
for i, bit := range nums {
x = (x<<1 | bit) % 5
ans[i] = x == 0
}
return ans
}
###js
var prefixesDivBy5 = function(nums) {
const ans = new Array(nums.length);
let x = 0;
for (let i = 0; i < nums.length; i++) {
x = ((x << 1) | nums[i]) % 5;
ans[i] = x === 0;
}
return ans;
};
###rust
impl Solution {
pub fn prefixes_div_by5(nums: Vec<i32>) -> Vec<bool> {
let mut ans = vec![false; nums.len()];
let mut x = 0;
for (i, bit) in nums.into_iter().enumerate() {
x = (x << 1 | bit) % 5;
ans[i] = x == 0;
}
ans
}
}
复杂度分析
- 时间复杂度:$\mathcal{O}(n)$,其中 $n$ 是 $\textit{nums}$ 的长度。
- 空间复杂度:$\mathcal{O}(1)$。返回值不计入。
分类题单
- 滑动窗口与双指针(定长/不定长/单序列/双序列/三指针/分组循环)
- 二分算法(二分答案/最小化最大值/最大化最小值/第K小)
- 单调栈(基础/矩形面积/贡献法/最小字典序)
- 网格图(DFS/BFS/综合应用)
- 位运算(基础/性质/拆位/试填/恒等式/思维)
- 图论算法(DFS/BFS/拓扑排序/基环树/最短路/最小生成树/网络流)
- 动态规划(入门/背包/划分/状态机/区间/状压/数位/数据结构优化/树形/博弈/概率期望)
- 常用数据结构(前缀和/差分/栈/队列/堆/字典树/并查集/树状数组/线段树)
- 数学算法(数论/组合/概率期望/博弈/计算几何/随机算法)
- 贪心与思维(基本贪心策略/反悔/区间/字典序/数学/思维/脑筋急转弯/构造)
- 链表、树与回溯(前后指针/快慢指针/DFS/BFS/直径/LCA)
- 字符串(KMP/Z函数/Manacher/字符串哈希/AC自动机/后缀数组/子序列自动机)
欢迎关注 B站@灵茶山艾府