环形数组技巧及实现

raw/articles/2026/05/环形数组技巧及实现

一句话总结

环形数组通过求模(余数)运算在逻辑上实现数组环形化,可将头尾增删元素的时间复杂度优化至 $O(1)$,核心是通过 startend 两个指针维护有效元素区间。

核心原理

  • 逻辑环形:数组本身是线性连续内存,通过 % 求模运算实现索引循环(如 i = (i + 1) % arr.length 可无限遍历数组)
  • 双指针维护start 指向第一个有效元素(闭区间),end 指向最后一个有效元素的下一个位置(开区间),形成左闭右开区间 [start, end)
  • 边界处理:指针移动超出数组边界时,通过求模运算自动回绕到数组另一端,无需数据搬移

时间复杂度优势

操作 普通数组 环形数组
头部增删 $O(N)$(需搬移元素) $O(1)$(仅移动 start 指针)
尾部增删 $O(1)$( amortized) $O(1)$
随机访问 $O(1)$ $O(1)$(需通过 start 计算真实索引)
指定索引增删 $O(N)$(需搬移元素) $O(N)$(与普通数组一致)

关键实现细节(Java)

  • 区间设计:采用左闭右开区间 [start, end),初始化 start = end = 0 时区间无元素,符合直觉
  • 指针移动规则
    • 头部添加:start = (start - 1 + size) % size(先左移再赋值)
    • 头部删除:先置空再 start = (start + 1) % size
    • 尾部添加:先赋值再 end = (end + 1) % size
    • 尾部删除:先左移 end 再置空
  • 自动扩缩容
    • 满时扩容为 2 倍(size * 2
    • 元素数降至容量 1/4 时缩容为 1/2(size / 2),避免频繁扩缩容
  • 泛型支持:Java 中通过 (T[]) new Object[size] 绕过泛型数组创建限制

局限性讨论

  • 标准库动态数组(如 Java ArrayList)未采用环形数组,因指定索引的增删操作仍需要 $O(N)$ 数据搬移,与普通数组无差异
  • 环形数组更适合频繁头尾操作的场景(如队列实现),通用场景下优势不明显

相关概念

相关实体


Interactive Graph