二维数组7:设计题,自己实现动态数组
Posted 纵横千里,捭阖四方
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了二维数组7:设计题,自己实现动态数组相关的知识,希望对你有一定的参考价值。
我们前面提到ArrayList是一个功能非常强大的数组,能够动态扩容、随机访问等等。我们现在来自己设计一个动态数组。为什么要自己设计呢?因为笔者曾经在面猿辅导的时候遇到这样的算法题:如果要你设计一个数据结构,你会怎么做。还曾经在面微博时,让设计一个RingBuffer的数据结构。
这种题目的难度很明显比那些烧脑题要低,但是更注重思考问题的完整性、通用性、灵活性、异常情况处理的完备性等。如果写出来的代码就是个学生demo,铁定是过不了的,这可能也是为什么大厂面试都喜欢面算法的一个原因吧。
这一节我们就设计一下动态数组,下一节设计RingBuffer。
动态数组的特点是:可随机存储,可以自动调整大小等。自动调整大小主要是动态扩容和缩容,因此这也是其最重要的特征。
另外一点,我们要设计一个完整的工具类,例如addLast和addFirst这种方法就要考虑,同时也要考虑代码的重用性,例如add方法就要足够完善,addLast和addFirst等能够直接调用。
当然面试的时候,要将整个设计都要和面试官说的,但是具体写的时候还是写几个重点方法,而不可能完全像库函数一样一字不漏。
直接看代码:
public class MyArray<E> {
private E[] data;
private int size;
/**
* 构造函数,传入数组的容量capacity构造Array
*/
public MyArray(int capacity) {
data = (E[]) new Object[capacity];
size = 0;
}
/**
* 无参数的构造函数,默认数组的容量capacity=10
*/
public MyArray() {
this(10);
}
//获取数组中的元素个数
public int getSize() {
return size;
}
// 获取数组的容量
public int getCapacity() {
return data.length;
}
// 返回数组是否为空
public boolean isEmpty() {
return size == 0;
}
// 向所有元素后添加一个新元素,O(1)
public void addLast(E e) {
add(size, e);
}
// 在所有元素前添加一个新元素,O(1)
public void addFirst(E e) {
add(0, e);
}
//在第index个位置插入一个新元素e,O(n/2)=O(n)
public void add(int index, E e) {
if (index < 0 || index > size)
throw new IllegalArgumentException("Add failed!,Require index >= 0 and index <= size.");
if (size == data.length)
resize(2 * data.length);
for (int i = size - 1; i >= index; i--)
data[i + 1] = data[i];
data[index] = e;
size++;
}
//获取index索引位置的元素,O(1)
public E get(int index) {
if (index < 0 || index >= size)
throw new IllegalArgumentException("Get failed.Index is illegal.");
return data[index];
}
//修改index索引位置的元素为e,O(1)
void set(int index, E e) {
if (index < 0 || index >= size)
throw new IllegalArgumentException("Set failed.Index is illegal.");
data[index] = e;
}
//查找数组中是否有元素e,O(n)
public boolean contains(E e) {
for (int i = 0; i < size; i++) {
if (data[i].equals(e))
return true;
}
return false;
}
// 查找数组中元素e所在的索引,如果不存在元素e,则返回-1,O(n)
public int find(E e) {
for (int i = 0; i < size; i++) {
if (data[i].equals(e))
return i;
}
return -1;
}
// 从数组中删除index位置的元素, 返回删除的元素,O(n)
public E remove(int index) {
if (index < 0 || index >= size)
throw new IllegalArgumentException("Remove failed.Index is illegal.");
E ret = data[index];
for (int i = index + 1; i < size; i++)
data[i - 1] = data[i];
size--;
data[size] = null;//loitering objects != memory leak
if (size == data.length / 2)
resize(data.length / 2);
return ret;
}
// 从数组中删除第一个元素, 返回删除的元素
public E removeFirst() {
return remove(0);
}
//从数组中删除最后一个元素, 返回删除的元素
public E removeLast() {
return remove(size - 1);
}
// 从数组中删除元素e
public void removeElement(E e) {
int index = find(e);
if (index != -1)
remove(index);
}
@Override
public String toString() {
StringBuilder res = new StringBuilder();
res.append(String.format("Array: size = %d , capacity = %d\\n", size, data.length));
res.append('[');
for (int i = 0; i < size; i++) {
res.append(data[i]);
if (i != size - 1)
res.append(",");
}
res.append(']');
return res.toString();
}
// 将数组空间的容量变成newCapacity大小
private void resize(int newCapacity) {
E[] newData = (E[]) new Object[newCapacity];
for (int i = 0; i < size; i++)
newData[i] = data[i];
data = newData;
}
}
我们再写个测试类看看:
public static void main(String[] args) {
MyArray<Integer> arr = new MyArray<Integer>();
for (int i = 0; i < 10; i++)
arr.addLast(i);
System.out.println(arr);
arr.add(1, 100);
System.out.println(arr);
arr.addFirst(-1);
System.out.println(arr);
arr.remove(2);
System.out.println(arr);
arr.removeElement(4);
System.out.println(arr);
arr.removeFirst();
System.out.println(arr);
}
输出结果是符合预期的,这里不再浪费页面截图了。
这里我们也可以自定义对象来测试,效果一样。
二.关于扩容和缩容的进一步说明
上面扩容和缩容都是在元素满了或者到了一般的时候进行,但是事实上应该占用空间超过四分之一就要考虑。
这里涉及到复杂度震荡问题,比较极端的一个情况是:
比如容量为10的一个数组,
此时该数组满了,此时要进来个元素,然后数组进行扩容,那么添加完元素此时数组的情况为容量为20,内部有11个元素。此时我再对数组进行删除一个元素,删除之后,数组元素个数变为10个,恰好为数组长度的二分之一,那么自动进行缩容,以此类推,反复操作,每次扩容缩容的时间复杂度为O(n),所以此处应用了lazy的解决方案就是等到数组元素个数为数组长度的四分之一时,再进行缩容,就可以避免这个问题。
这种方法在HashMap等中都有大量应用,后面遇到的时候我们再详谈。
以上是关于二维数组7:设计题,自己实现动态数组的主要内容,如果未能解决你的问题,请参考以下文章