数组的内存

数组在内存中的存储方式及其核心特性可以概括为以下几点:

1. 内存存储方式

  • 连续内存空间:数组是存放在连续内存空间上的相同类型数据的集合,。
  • 线性排列:由于内存地址是连续的,数组可以方便地通过下标索引(Index)快速获取对应的数据,。
  • 多维数组的特殊性:在 Java 中,二维数组(如 int[][] rating = new int)并不是一个完全连续的 $3 \times 4$ 地址空间,而是由四条连续的地址空间组成的,。而在 C++ 中,二维数组在内存地址上则是连续的。

2. 核心特性

  • 下标从 0 开始:这是数组最基本的寻址规则,。
  • 元素不可“物理删除”:数组的元素在内存中是不能真正删除的,只能通过覆盖的方式来达到逻辑上的删除效果,。
  • 增删成本高:正是因为内存地址连续,在进行删除或增加操作时,难免要移动其他元素的地址,。例如,删除下标为 3 的元素,需要将其后所有的元素都向前移动一位。
  • 容器与底层的区别:以 C++ 为例,虽然 vector 使用起来很方便,但它的底层实现依然是 array;严格来说,vector 是容器,而 array 才是原生数组,。

算法精要

1. 二分法 (Binary Search) 与循环不变量

• 深度解析:二分法的难点不在于逻辑,而在于边界处理 [1, 2]。源码强调了 “循环不变量原则”,即在循环过程中,对区间的定义(如左闭右闭 [left, right] 或左闭右开 [left, right))必须始终保持一致 [2, 3]。

• 性能提升:通过不断折半查找空间,将时间复杂度从暴力扫描的 O(n) 降低到 O(logn) [2, 3]。

只推荐使用区间左闭右闭,其他区间都有些边界条件需要讨论,左闭右闭最方便。

2. 双指针法 (Two Pointers)

• 核心逻辑:通常指快慢指针。通过一个快指针和慢指针在同一个 for 循环下工作,快指针负责寻找新元素,慢指针负责指向更新位置 [2, 3]。

• 解决痛点:由于数组元素在内存中是连续的,无法直接删除,只能覆盖 [3, 4]。双指针法能在一层循环内完成原本需要两层嵌套循环(暴力解法 O(n2))的工作,从而达到 O(n) 的效率 [3, 4]。

3. 滑动窗口 (Sliding Window)

• 精妙之处:它本质上也是双指针,但侧重于动态调节子序列的起始和终止位置 [4, 5]。

• 应用场景:常用于寻找满足某种条件的“最小长度子数组”等问题。窗口的起始位置会根据当前窗口内元素之和(或其它指标)的情况进行移动,从而将 O(n2) 的复杂度降为 O(n) [4, 5]。

4. 模拟行为 (Simulation)

• 考察点:这类题目(如螺旋矩阵)不涉及高深算法,主要考察对代码的掌控能力 [5, 6]。

• 实现细节:同样需要严格遵循循环不变量原则 [5, 6]。如果处理边界时没有统一的规则(如每一条边都处理左闭右开),代码会变得极其冗余且容易出错 [5, 6]。

5. 前缀和 (Prefix Sum)

• 核心价值:专门用于解决区间求和问题 [5, 6]。

• 思路转变:虽然思路简单,但如果不了解这个技巧,很难想到如何高效地处理频繁的区间查询问题,是开阔解题维度的重要工具 [5, 6]。

总结

数组总结图