二维数组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:设计题,自己实现动态数组的主要内容,如果未能解决你的问题,请参考以下文章

第84题二维数组的动态内存申请 | malloc 的应用

二维数组8:设计题 RingBuffer的原理和实现

c语言中怎样实现对二维数组元素进行赋值并输出。

[编程题]二维数组中的查找

计算机二级-C语言-程序设计题-190119记录-求出一个二维数组每一列的最小值。

[编程题] lk [152. 乘积最大子数组-二维动态规划]