我最近在刷牛客网的前端题,刷到FED37数组反转的时候,第一反应是:这也太简单了吧,JS里一个reverse()不就完事了?但等我真正打算把这题吃透、写一个HTML页面来演示反转过程的时候,才发现里面有不少值得掰开揉碎讲的东西。加上这几天我一直在手机腾讯元宝里用DeepSeek的深度思考模式辅助刷题,这题就是典型的"看着简单、越挖越深"的样本。这篇文章就把我的完整实操过程记录下来:从FED37的考点拆解,到多种反转实现,到整份HTML页面代码,再到我用DeepSeek辅助推理时的对话实录和踩坑心得。不管是刚准备笔试的初学者,还是在准备面试的前端开发者,都能从中找到自己能直接拿去用的东西。
1. 腾讯元宝里的DeepSeek,怎么就成了我的刷题搭子
1.1 深度思考模式,对我这种"一知半解"的人最有价值
先说工具。牛客网刷题时,我手机里常年挂着腾讯元宝,模型直接切到DeepSeek,平时遇到看不明白的题解、没搞懂的知识点,就往里一扔。相比普通AI问答模式,DeepSeek的"深度思考"模式最打动我的一点是:它不会直接甩答案,而是在回答前先输出一大段推理过程,把"一个有点经验的工程师看到这道题之后脑子里闪过的东西"完完整整摊开给你看。
这正好治我的老毛病——以前刷题经常出现"看答案全懂,合上答案就懵"的情况。普通问答模式只告诉我"用reverse()就行",深度思考模式则会告诉我"为什么能想到用reverse()""如果不能用API怎么办""题目里这句话是不是在暗示原地反转"。这些东西,恰恰是面试时最值钱的部分。
1.2 用元宝刷题前的准备和一个小坑
配置上没什么门槛,手机装好腾讯元宝,登录后用模型切换功能把默认模型切到DeepSeek即可。网页端和App端都能用,我惯用App,因为刷题时随手拍照、复制题目片段都方便得多。
不过我第一次切换时确实弹过一个"安装环境异常"的提示,当时以为是模型包没下好,后来发现多半是版本旧了或者网络抽风,把App升级到最新版本、清理一次缓存再重开,问题就消失了。如果你也遇到类似提示,先别慌,升级版本和换个网络环境是最高频的解法。
准备工作做完之后,我就拿着牛客FED37这道题进场了。
2. FED37数组反转,题目背后藏着哪些考点
2.1 先还原一下原题长什么样
牛客网的FED系列是前端方向的专项练习,FED37这题属于数组基础操作。题目描述大致是这样:
将数组 arr 中的元素反转。例如输入 [1, 2, 3, 4],输出 [4, 3, 2, 1]。 要求实现一个函数,优先考虑在原数组上操作,不要借助额外的数组空间。
平台要求的交付物是一个函数,形如:
function reverseArray(arr) { // 在这里补全代码 }函数返回的结果会被判定器拿去和预期输出比对。这题在牛客上的通过率并不低,但有意思的地方在于:越是这种基础题,越能拉开人和人的差距。有人一行reverse()交上去完事,有人会追问"题目里'在原数组上操作'到底是什么意思",还有人能主动把时间复杂度和空间复杂度说清楚——后者在面试里拿到的评价,通常比前者高一个档次。
2.2 考点拆解:不只考API,更考原理
把FED37放在面试官视角下,它其实至少覆盖了三个层面的知识点:
第一,数组API的熟练度。reverse()是不是存在、怎么用、返回值是什么,这是最表层的东西。
第二,引用类型的理解。JavaScript里数组是引用类型,函数的参数传的是引用。你在函数里改了实参,外面的原数组也会跟着变。很多人在"原地反转"和"复制后反转"之间傻傻分不清,本质是对引用传递的理解不够透。
第三,手写算法的基本功。当题目要求"不要用额外数组"时,能不能立刻想到双指针交换,就检验你有没有真正写过底层逻辑了。这也是面试官最喜欢追问的方向。
3. 先别急着写HTML,把反转函数本身吃透
3.1 解法一:一行reverse()的利弊
function reverseArray(arr) { return arr.reverse(); }这是最直观的写法。JS引擎(比如V8)对reverse()做了底层优化,性能很好,代码可读性也最高。但这里有两个隐藏知识点,恰恰是新手最容易翻车的地方。
第一个:reverse()是原地反转,它操作的是原数组本身,同时返回的值也是这个数组的引用。也就是说arr.reverse()执行完,你手里的arr已经变了。这看起来在"原地操作"的题目要求下反而是优势,但在日常开发里如果你不希望原数据被修改,这就很危险。
第二个:牛客判定器到底认不认一行reverse()。实话实说,很多写题平台是认的,但只要面试官坐在你对面,他大概率会追问一句"如果不用原生reverse(),你怎么实现"。到这一步,卡壳的人特别多。
3.2 解法二:双指针原地交换的手写实现
双指针是手写反转的标准答案,也是我最终在HTML页面里采用的方案:
function reverseArray(arr) { let left = 0; let right = arr.length - 1; while (left < right) { // 交换左右指针对应的元素 const tmp = arr[left]; arr[left] = arr[right]; arr[right] = tmp; left++; right--; } return arr; }原理一句话:一个指针指向头,一个指针指向尾,每次交换两个位置的元素,然后头指针往右走一步、尾指针往左走一步,直到两个指针相遇或者错位。整个过程中只用到了一个临时变量tmp,空间复杂度是O(1),完全满足"不借助额外数组空间"的约束。
我第一版写的条件其实是while (left <= right),自己测的时候发现没啥问题,但后来看DeepSeek的思考链里特意提醒了一句"等于的时候不需要交换"——我琢磨了一下,确实,如果数组长度是奇数,两个指针最终会同时指向中间那个元素,它和自己交换等于白做一次操作。用<既省一次循环,逻辑上也更干净。
3.3 解法三:不修改原数组的"反转变体"
面试官非常容易追问的另一个方向是:如果我不想改变原数组,只想要一个新数组,你会怎么写?
常见的实现有三种:
// 方式1:slice先复制,再reverse function reverseArray(arr) { return arr.slice().reverse(); } // 方式2:反向遍历,逐个push function reverseArray(arr) { const result = []; for (let i = arr.length - 1; i >= 0; i--) { result.push(arr[i]); } return result; } // 方式3:reduce + spread(偏炫技,不推荐在面试里主动用) function reverseArray(arr) { return arr.reduce((acc, cur) => [cur, ...acc], []); }这三种的共同点是都不会动原数组。注意arr.slice().reverse()里那个slice()很关键——先复制出一个新数组,再在新数组上反转。如果你写的是arr.slice().reverse(),那是安全的;如果你写arr.reverse(),原数组就没了。
3.4 三种方案怎么选:一个表格说清楚
| 实现方案 | 是否修改原数组 | 时间复杂度 | 空间复杂度 | 最适用的场景 |
|---|---|---|---|---|
| arr.reverse() | 是 | O(n)(引擎层已优化) | O(1) | 明确要求原地反转,追求简洁时 |
| 双指针手写 | 是 | O(n) | O(1) | 面试手写算法,展示原理理解 |
| slice().reverse() | 否 | O(n) | O(n) | 生产环境需要保留原数据时 |
| 反向遍历push | 否 | O(n) | O(n) | 想展示不依赖API也能实现时 |
我的建议是:平时刷题四种都要会写,面试时优先给双指针方案,然后把reverse()的实现原理和"原地修改"这个特性作为补充信息主动说出来,面试官的好感度会明显不一样。
4. 完整的HTML实现:输入、反转、展示一条龙
4.1 页面结构与交互逻辑怎么设计
牛客OJ上只要交函数就行,但既然标题写了"HTML实现",那就把整个交互页做出来。我的设计思路很直接:
- 一个输入框,让用户输入数组元素,用英文逗号分隔
- 一个按钮,点击后触发反转逻辑
- 一个结果展示区域,逆序渲染出反转后的数组
交互逻辑的核心是输入解析:拿到输入框的字符串后,先按逗号split成数组,再做一次trim去掉空格,顺手用Number()尝试转成数字,转不了的就保留原字符串。这样用户输入"1,2,3,4"得到数字数组,输入"a,b,c"也能正常反转字符串数组,容错性好得多。
4.2 完整可运行的HTML代码
下面是我实际跑通的完整页面,直接存成.html文件双击就能用:
<!DOCTYPE html> <html lang="zh-cn"> <head> <meta charset="utf-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>FED37 数组反转练习</title> <style> * { box-sizing: border-box; margin: 0; padding: 0; } body { font-family: "PingFang SC", "Microsoft YaHei", sans-serif; background: #f5f6fa; display: flex; justify-content: center; align-items: center; min-height: 100vh; } .card { background: #fff; border-radius: 12px; padding: 32px; width: 420px; box-shadow: 0 4px 16px rgba(0, 0, 0, 0.08); } .card h1 { font-size: 20px; margin-bottom: 8px; color: #1a1a2e; } .card .desc { font-size: 14px; color: #888; margin-bottom: 20px; } .card label { display: block; font-size: 14px; margin-bottom: 6px; color: #333; } .card input { width: 100%; padding: 10px 12px; border: 1px solid #ddd; border-radius: 6px; font-size: 14px; margin-bottom: 12px; outline: none; } .card input:focus { border-color: #2d6cdf; } .card button { width: 100%; padding: 10px 0; background: #2d6cdf; color: #fff; border: none; border-radius: 6px; font-size: 15px; cursor: pointer; margin-bottom: 16px; transition: background 0.2s; } .card button:hover { background: #1e56b3; } .result-box { background: #f0f4ff; border-radius: 6px; padding: 16px; font-size: 16px; color: #1a1a2e; min-height: 60px; } .result-box .tag { font-size: 12px; color: #888; margin-bottom: 4px; } #result { word-break: break-all; } </style> </head> <body> <div class="card"> <h1>FED37 数组反转</h1> <p class="desc">牛客网前端练习 · 输入数组元素,点击按钮反转</p> <label for="arrayInput">数组(用英文逗号分隔)</label> <input type="text" id="arrayInput" placeholder="例如:1,2,3,4" value="1,2,3,4"> <button id="reverseBtn">反转数组</button> <div class="result-box"> <div class="tag">反转结果</div> <div id="result">等待输入...</div> </div> </div> <script> // 双指针原地反转,与牛客FED37要求一致 function reverseArrayInPlace(arr) { let left = 0; let right = arr.length - 1; while (left < right) { const tmp = arr[left]; arr[left] = arr[right]; arr[right] = tmp; left++; right--; } return arr; } document.getElementById('reverseBtn').addEventListener('click', function () { const input = document.getElementById('arrayInput').value.trim(); const resultBox = document.getElementById('result'); if (!input) { resultBox.textContent = '请输入数组元素'; return; } const arr = input.split(',').map(function (item) { const trimmed = item.trim(); const num = Number(trimmed); return isNaN(num) ? trimmed : num; }); const reversed = reverseArrayInPlace(arr); resultBox.textContent = '[' + reversed.join(', ') + ']'; }); </script> </body> </html>4.3 跑起来之后要注意的细节
我实际测试时发现几个容易被忽略的点:
第一,输入框里的空格处理。很多用户会习惯性输入"1, 2, 3"这种带空格的格式,如果直接split(','),得到的数组是["1", " 2", " 3"],反转后输出就带着空格,很丑。所以map阶段一定要做trim()。
第二,空输入不能崩。我在按钮事件里先做了trim(),如果为空就直接提示"请输入数组元素",避免后面对一个空字符串做split(',')得到[""]这种脏数据。
第三,"反转数组"和"反转字符串"千万别混。我在测试时一度想复用这页代码去反转一个纯字符串,结果发现"abc".split(',')根本切不动——字符串反转是另一个思路,需要先split('')切分成字符数组再反转。这俩问题在面试里也经常被拿来混淆提问。
5. 我在元宝里和DeepSeek的真实对话记录
5.1 我是怎么把牛客题喂给AI的
与其空谈AI辅助刷题,不如直接放一段我实际使用的提问过程。当时我把题目和我的困惑整理成一段话,扔给了元宝里的DeepSeek:
我在牛客网刷FED37数组反转。题目是:将数组arr中的元素反转,例如[1,2,3,4]变为[4,3,2,1],要求实现一个函数,尽量在原数组上操作,不借助额外数组空间。我直接写arr.reverse()交上去会不会有问题?如果面试官追问手写实现,我应该怎么答?
这里有个经验:提问时一定要把"题目来源+完整要求+我的主观困惑"三样都说清楚。只贴一行题面,AI给出的答案大概率是泛泛的;把"我在牛客上刷的""要求原地反转"这些上下文带上,它才能给出针对性的分析。
5.2 DeepSeek思考链里我印象最深的三个点
DeepSeek在深度思考模式下先输出了一段推理,我不是逐字复制的,但它在推理链里提到的三个点让我印象很深。
第一,它主动区分了"牛客判定器的验收标准"和"面试官的考察标准"。它的意思是:如果牛客只比对返回结果,reverse()大概率能过;但面试场景里,考察的是你能不能脱离API手写核心逻辑。所以稳妥的策略是会写reverse(),更要会写双指针。
第二,它提醒我注意reverse()的副作用——"原地反转"既是题目的要求,也是一个隐性陷阱。如果题目哪天变成"返回一个新的反转数组",直接调reverse()就会把原数组搞坏。这让我意识到:刷题不能只追求答案对,要考虑题目背后对数据变动的隐含约定。
第三,它补充了"偶数长度/奇数长度数组对终止条件的影响"。奇数个数时,最中间的指针指向自己;偶数个数时,两个指针会擦肩而过。这就解释了为什么双指针的循环条件要用left < right而不是left <= right——等号在数学上没毛病,工程上却多了一轮无意义操作。
5.3 顺着AI的回答挖出的面试追问
DeepSeek在回答末尾还主动列了几个"面试官可能会追问"的问题,我原封不动记下来了:
- 如果数组里放的是对象,反转后对象的引用关系会变吗
- 怎样在不修改原数组的前提下拿到反转结果
reverse()的时间复杂度是多少,手写双指针的时间复杂度又是多少- 如果输入不是数组,而是一个类数组对象,你的函数会出什么问题
这几个问题我之前从来没想过。第一个问题的答案是:反转的是数组元素的排列顺序,元素如果是对象,对象的引用本体不会被复制或深拷贝,也就是说反转前后数组里每个元素的引用地址不变,只是顺序变了。想明白这一点,很多"数组套对象"的题目思路都会清爽很多。
6. 面试官视角:数组反转的加分项与避坑清单
6.1 高频追问和不翻车回答
把FED37放到实战面试里,我整理了一套高频追问的应答思路:
追问1:你用的reverse()会改变原数组吗?回答要点:会。reverse()是原地操作,返回的也是原数组的引用。所以如果要求保留原数据,得先slice()再reverse()。
追问2:手写一个原地反转,说说复杂度和边界。回答要点:双指针。左右指针向中间靠拢,交换元素,循环条件left < right。时间复杂度O(n),空间复杂度O(1)。边界上注意空数组、单元素数组、奇数长度数组。
追问3:如果输入是[1, 5, 3, 9],你的代码跑完是什么过程?回答要点:当场画状态最好,口头描述也行——首尾交换(9和1换)、指针内缩(5和3换)、left=right停止,得到[9, 3, 5, 1]。这题一旦能脱口而出,面试官基本就知道你是真的理解了。
追问4:数组元素是对象,反转后foreach遍历顺序变成了什么?回答要点:遍历顺序变成从最后一个元素到第一个元素,但每个元素的对象引用没变。如果需要深拷贝,那是另一套方案。
6.2 数组反转避坑清单
| 坑 | 出现原因 | 正确处理 |
|---|---|---|
| 以为reverse()返回一个新数组 | 对返回值理解不到位 | 它返回的是原数组的引用,原数组已经被修改 |
双指针循环条件写成了<= | 没想清中间指针是否需要自交换 | 用<,等于时停止 |
| split(',')后元素带空格 | 用户输入"1, 2, 3" | map时先trim()再处理 |
| 输入空串导致数组变[""] | 没有提前判空 | trim后为空直接return |
| 数字与字符串混用时转成了NaN | 无脑Number() | 用isNaN兜底,转不了就保留原字符串 |
| 以为对象会被"反转时被复制" | 对引用类型理解不透 | 反转只改变顺序,不复制对象本身 |
6.3 关于AI辅助刷题,我的几条实操心得
写到这里,说点个人的真实体会。AI辅助刷题确实效率高,但前提是你要带着脑子用。我的三条原则很简单:
第一,先自己动手写,再让AI点评。直接甩题给AI要答案,短期刷题数量上去了,面试一紧张照样写不出来。我自己是这个流程:先在牛客上提交一版,再把我和AI的代码对比,让它帮我找出"我写的和它写的差在哪、为什么差"。
第二,主动要求AI暴露思考链。在元宝里用DeepSeek时,深度思考模式默认会展开推理过程,这一步千万别跳过。很多题的价值不在最终代码,而在"看到题目后如何一步步接近答案"的推理路径。把推理链看懂,比拿到正确代码有用十倍。
第三,把AI当成出题陪练。我习惯在解完一题后让它换个条件再问我,比如"如果数组里有空值""如果要求递归实现""如果数组长度是百万级性能怎么办"。这些问题刷完一遍,回头再看FED37这种基础题,基本不会再有死角。
我自己的体会是,这题尽管叫"数组反转",真正的价值在于把API调用、引用类型理解、手写算法能力以及边界处理这几个基本功串在了一起。能独立把这些讲明白,说明这题没有白刷。