自己实现一个CycleArray
- 前面的用环形数组实现的栈和队列就是基于这个环形数组的。
核心原理
环形数组的关键在于,它维护了两个指针 start 和 end,start 指向第一个有效元素的索引,end 指向最后一个有效元素的下一个位置索引。
这样,当我们在数组头部添加或删除元素时,只需要移动 start 索引,而在数组尾部添加或删除元素时,只需要移动 end 索引。
当 start, end 移动超出数组边界(< 0 或 >= arr.length)时,我们可以通过求模运算 % 让它们转一圈到数组头部或尾部继续工作,这样就实现了环形数组的效果。
-
在我的代码中,环形数组的区间被定义为左闭右开的,即 [start, end) 区间包含数组元素。所以其他的方法都是以左闭右开区间为基础实现的。
-
因为这样初始化 start = end = 0 时区间 [0, 0) 中没有元素,但只要让 end 向右移动(扩大)一位,区间 [0, 1) 就包含一个元素 0 了。
-
如果你设置为两端都开的区间,那么让 end 向右移动一位后开区间 (0, 1) 仍然没有元素;如果你设置为两端都闭的区间,那么初始区间 [0, 0] 就已经包含了一个元素。这两种情况都会给边界处理带来不必要的麻烦。
public class CycleArray<T> {
private T[] arr;
private int start;
private int end;
private int count;
private int size;
public CycleArray(){
this(1);
}
@SuppressWarnings("unchecked")
public CycleArray(int size){
this.size = size;
this.arr = (T[]) new Object[size];
this.start = 0;
this.end = 0;
this.count = 0;
}
@SuppressWarnings("unchecked")
private void resize(int newSize){
T[] newArr = (T[]) new Object[newSize];
for(int i = 0; i < count; i++){
newArr[i] = arr[(start + i) % size];
}
arr = newArr;
start = 0;
end = count;
size = newSize;
}
public void addFirst(T val){
if(isFull()){
resize(size * 2);
}
start = (start - 1 + size) % size;
arr[start] = val;
count++;
}
public T removeFirst(){
if(isEmpty()){
throw new IllegalStateException("Array is empty");
}
T t = arr[start];
arr[start] = null;
start = (start - 1) % size;
count--;
if(count > 0 && count == size / 4){
resize(size / 2);
}
return t;
}
public void addLast(T val){
if(isFull()){
resize(size * 2);
}
arr[end] = val;
end = (end + 1) % size;
count++;
}
public T removeLast(){
if(isEmpty()){
throw new IllegalStateException("Array is empty");
}
end = (end - 1) % size;
T t = arr[end];
arr[end] = null;
count--;
if(count > 0 && count == size /4){
resize(size / 2);
}
return t;
}
public T getFirst(){
if(isEmpty()){
throw new IllegalStateException("Array is empty");
}
return arr[start];
}
public T getLast(){
if(isEmpty()){
throw new IllegalStateException("Array is empty");
}
return arr[(end - 1 + size) % size];
}
public boolean isFull(){
return count == size;
}
public int size(){
return count;
}
public boolean isEmpty(){
return count == 0;
}
}
-
数组增删头部元素的效率真的只能是 O(N) 么?
-
我们都说,在数组增删头部元素的时间复杂度是 O(N),因为需要搬移元素。但是,如果我们使用环形数组,其实是可以实现在 O(1) 的时间复杂度内增删头部元素的。
-
当然,上面实现的这个环形数组只提供了 addFirst, removeFirst, addLast, removeLast 这几个方法,并没有提供 我们之前实现的动态数组 的某些方法,比如删除指定索引的元素,获取指定索引的元素,在指定索引插入元素等等。