news 2026/4/15 19:11:16

从零开始搞懂时间/空间复杂度 + 图解三指针合并有序数组(力扣88题)

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
从零开始搞懂时间/空间复杂度 + 图解三指针合并有序数组(力扣88题)

一、什么是时间复杂度和空间复杂度?——用5段代码讲明白

在算法世界里,我们不只关心“能不能跑通”,更关心“跑得快不快”、“占不占地方”。这就是时间复杂度空间复杂度要解决的问题。

时间复杂度:衡量“执行步骤”的增长趋势

不是看实际运行几毫秒,而是看随着输入数据变大,操作次数怎么变。我们用大O表示法(Big O)来描述这个“趋势”。

来看几个经典例子:


例1:线性遍历 →O(n)

来自1.js

// T(n) = 3n+3 function traverse(arr) { var len = arr.length; // 1次 for (let i = 0; i < len; i++) { // 循环 n 次 console.log(arr[i]); // 每次循环1次 } } // T(n) = 1 + 1 + n + 1 + n + n = 3n+3 → 忽略常数和低阶项 → O(n)

解释:数组越长,打印次数越多,成正比关系。就像你数100个人的名字,肯定比数10个人花10倍时间。


例2:双重循环 →O(n²)

来自2.js

function traverse(arr) { var outlen = arr.length; for (var i = 0; i < outlen; i++) { var inlen = arr[i].length; for (var j = 0; j < inlen; j++) { console.log(arr[i][j]); } } } // T(n) ≈ 4n² + ... → O(n²)

解释:比如一个 n×n 的表格,你要把每个格子都点一遍,总共点 n² 次。数据量翻倍,操作次数变成4倍!很可怕。


例3:指数级跳跃 →O(log n)

来自3.js

for (var i = 1; i < len; i = i * 2) { console.log(arr[i]); } // T(n) = 2logn + 4 → O(log n)

解释:i 每次翻倍(1→2→4→8...),所以循环次数是 log₂(len)。比如 len=1024,只循环10次!这是非常高效的节奏,像二分查找。


例4:额外开数组 →O(n)空间

来自4.js

function init(n) { var arr = []; // 新开辟空间! for (var i = 0; i < n; i++) { arr[i] = i; } return arr; } // S(n) = O(n)

空间复杂度:衡量额外内存占用。这里新建了一个长度为 n 的数组,所以空间是 O(n)。


例5:原地操作 →O(1)空间

还是1.js

// S(1) 因为只有一个arr,其他作为参数,没有额外的内存开销

没有 new 数组、没递归、没哈希表,只是用几个变量(i, len),所以空间是常数级O(1) —— 最省!


总结一句话

  • 时间复杂度:看“操作次数”随输入规模怎么涨。
  • 空间复杂度:看“额外内存”用了多少。
  • 我们追求:时间快 + 空间省

二、实战:力扣88题《合并两个有序数组》——三指针 + 原地合并

题目要求:

  • nums1长度为m + n,前m个是有用数字,后n个是 0(预留空间)
  • nums2长度为n
  • nums2合并进nums1,最终nums1变成一个有序数组
  • 不能返回新数组!必须原地修改 nums1

错误思路:从前向后合并?

很多人第一反应:用两个指针从前往后比,小的放前面。

但问题来了:nums1 后面有空位,前面却有数据!
如果你把小的数往前面插,会覆盖掉还没处理的 nums1 元素

比如:

nums1 = [1,2,3,0,0,0], m=3 nums2 = [2,5,6], n=3

如果从前往后,第一步把 1 放好没问题,但第二步要把 2(来自 nums2)放进去时,nums1[1] 原本是 2,会被覆盖,而那个 2 还没被处理!

所以不能从前向后


正确思路:从后往前 + 三指针

利用 nums1尾部有空位的特点,从最大的数开始填,就不会覆盖未处理的数据!

来看代码(一字不变):

function merge(nums1, m, nums2, n) { // 数组是连续的存储空间,所以可以从后往前合并 let i = m - 1; // i 是 nums1 里面“有用数字”的最后一位的位置(从0开始数) let j = n - 1; // j 是 nums2 里面“有用数字”的最后一位的位置 let k = m + n - 1; // k 是 nums1 整个数组最后一位的位置(因为nums1已经预留了足够空间) // 数组是有序的 while(i >= 0 && j >= 0) { // 只要 nums1 和 nums2 都还有数字没比完,就继续比 if (nums1[i] > nums2[j]) { // 如果 nums1 当前的数字比 nums2 的大 nums1[k] = nums1[i]; // 就把大的那个放到 nums1 的最后面(k的位置) i--; // 然后 nums1 的指针往前面走一步(看下一个数字) } else { // 否则(nums2 的数字更大或一样大) nums1[k] = nums2[j]; // 把 nums2 的这个数字放到 nums1 的最后面 j--; // 然后 nums2 的指针往前面走一步 } k--; // 不管放了谁,最后面的位置都要往前挪一格,准备放下一个 } while(j >= 0) { // 如果 nums2 还剩下一些小数字没放完(nums1已经放完了) nums1[k] = nums2[j]; // 就把这些剩下的小数字一个个放到 nums1 前面空着的位置 j--; // 每放一个,nums2 的指针往前走 k--; // 放的位置也往前走 } }

图解三指针工作过程

初始状态:

nums1 = [1, 2, 3, 0, 0, 0] ↑ ↑ ↑ i | k | nums2 = [2, 5, 6] ↑ j

Step 1: 比较 nums1[i]=3 和 nums2[j]=6 → 6 更大 → 放到 k 位置

nums1 = [1, 2, 3, 0, 0, 6] ↑ ↑ ↑ i | k | nums2 = [2, 5, 6] ↑ j

Step 2: 比较 3 vs 5 → 5 更大

nums1 = [1, 2, 3, 0, 5, 6] ↑ ↑ ↑ i | k | nums2 = [2, 5, 6] ↑ j

Step 3: 比较 3 vs 2 → 3 更大

nums1 = [1, 2, 3, 3, 5, 6] ↑ ↑ ↑ i | k | nums2 = [2, 5, 6] ↑ j

Step 4: 比较 2 vs 2 → 相等,放 nums2 的(或 nums1 的也行)

nums1 = [1, 2, 2, 3, 5, 6] ↑ ↑↑ i k nums2 = [2, 5, 6] (j=-1,结束)

此时j < 0,说明 nums2 已全部放入。而 nums1 剩下的 [1,2] 本来就在正确位置,不用动!

💡 注意:如果 nums1 先耗尽(i<0),但 nums2 还有剩,就需要第二个 while 循环把剩下的 nums2 元素补到前面。


复杂度分析

  • 时间复杂度:O(m + n)
    每个元素最多被访问一次,总共 m+n 个元素。

  • 空间复杂度:O(1)
    只用了 i, j, k 三个变量,没有额外数组!完美符合“原地合并”要求。


为什么这个方法聪明?

  1. 利用了“有序”特性:最大值一定在两个数组的末尾。
  2. 利用了“预留空间”:从后往前写,不会踩到自己的脚。
  3. 三指针分工明确
    • i:负责 nums1 的有效数据
    • j:负责 nums2 的所有数据
    • k:负责写入位置

三、结语:算法之美,在于洞察结构

这道题看似简单,却完美展示了:

  • 如何通过分析数据结构特点(有序 + 尾部空闲)设计高效策略;
  • 如何用极低的空间代价(O(1))完成任务;
  • 为什么时间复杂度 O(m+n)是最优的(每个元素至少要看一次)。

下次遇到“合并有序数组”,别再想着新建数组了!试试从后往前,三指针出击

🌟记住:好的算法,不是代码多炫,而是恰到好处地利用已知条件

Happy Coding!🚀

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/4/16 16:04:09

LangFlow增量静态再生(ISR)应用场景

LangFlow 与增量静态再生&#xff1a;让 AI 工作流高效落地 在构建 AI 应用的今天&#xff0c;一个常见的困境是&#xff1a;模型能力越来越强&#xff0c;但把它们变成用户真正能用的产品却依然困难重重。我们花大量时间写代码、调接口、处理数据流&#xff0c;而业务方还在等…

作者头像 李华
网站建设 2026/4/16 8:59:37

Open-AutoGLM脱敏规则进阶配置(仅限内部分享的7种高级模式)

第一章&#xff1a;Open-AutoGLM 数据脱敏规则定制在构建企业级大模型应用时&#xff0c;数据安全与隐私保护是不可忽视的核心环节。Open-AutoGLM 提供了一套灵活可扩展的数据脱敏机制&#xff0c;支持用户根据业务场景自定义脱敏规则&#xff0c;确保敏感信息在模型训练与推理…

作者头像 李华
网站建设 2026/4/16 14:06:26

为什么你的敏感数据识别总失败?Open-AutoGLM五大优化策略首次披露

第一章&#xff1a;为什么你的敏感数据识别总失败&#xff1f;企业在实施数据安全策略时&#xff0c;常依赖敏感数据识别技术来发现和分类关键信息。然而&#xff0c;许多组织发现其识别准确率远低于预期。问题根源往往不在于工具本身&#xff0c;而在于实施过程中的常见误区。…

作者头像 李华
网站建设 2026/4/16 6:47:25

【数据安全新纪元】:基于Open-AutoGLM的敏感信息识别优化方案全公开

第一章&#xff1a;数据安全新纪元的挑战与机遇随着云计算、人工智能和物联网技术的迅猛发展&#xff0c;数据已成为企业最核心的资产之一。然而&#xff0c;数据规模的爆炸式增长也带来了前所未有的安全挑战。传统防火墙与加密手段已难以应对日益复杂的网络攻击&#xff0c;零…

作者头像 李华
网站建设 2026/4/16 14:40:55

为什么90%的AI系统存在隐私漏洞?Open-AutoGLM审计方案一文讲透

第一章&#xff1a;为什么90%的AI系统存在隐私漏洞&#xff1f;人工智能在医疗、金融和社交平台等关键领域广泛应用&#xff0c;但随之而来的隐私风险却常被忽视。研究表明&#xff0c;超过90%的AI系统在数据处理或模型训练阶段存在潜在隐私泄露路径&#xff0c;其根源往往并非…

作者头像 李华
网站建设 2026/4/16 10:38:45

Open-AutoGLM HTTPS加密失败应急处理(99%的人都忽略的关键步骤)

第一章&#xff1a;Open-AutoGLM HTTPS加密失败的根源剖析在部署 Open-AutoGLM 框架时&#xff0c;HTTPS 加密连接频繁出现握手失败或证书验证异常的问题&#xff0c;已成为影响系统安全通信的主要障碍。此类问题通常并非由单一因素引起&#xff0c;而是多层配置与环境交互的结…

作者头像 李华