数据结构——数组(一)

数组

1. 数组基础

介绍

  1. 有限个相同类型的变量组成的有序集合(军训,固定的人在固定的位置)

  2. 顺序存储结构

  3. 不同类型的数组每个元素所占的字节数也不同

基本操作

  1. 读取 O(1)

  2. 更新 O(1)

  3. 插入 O(n)

    1. 尾插(直接在数组的空元素位置插入元素)
    2. 中插(在数组的中间位置插入元素,插入位置之后的元素要统一往后移动一个位置)
    3. 超范围插(数组的位置不够,创建一个新的数组覆盖原来的数组,新数组长度最后是数组的二倍)
  4. 删除 O(n)

    1. 中间位置删除(删除之后,之后的元素统一向前移动一个单位)
    2. 末位置删除

数组的优劣

优:快速查询,高效的随机访问,可根据下表找到对应的元素。

缺:增加,删除操作的数据大量迁移,严重影响效率

使用范围

适合那些查,改操作多,增删操作较少的场景。

  1. 数组最大优点:快速查询。scores[2]

  2. 数组最好应用于“索引有语意”的情况

  3. 但并非所有有语意的索引都是用于数组 如身份证号:410181195112112511

  4. 数组的默认取值

image-20211225212445825

2. 数组使用

0. 引入问题

image-20211225212638751

问题:

  1. 索引没有语意,如何表示没有元素?
  2. 规定长度为8,如果有九个或者更多元素,如何添加元素?删除元素?
  3. 。。。。。。

解决:

  1. 由于java并没有给我们提供解决以上问题的方法,所以我们需要自己去解决

  2. java数组属于静态数组,解决上面的问题我们需要基于java封装自己的动态数组

1. 创建数组

  1. 创建一个int类型的空数组
  2. 创建一个成员变量size(用于记录数组中元素的数量) 非数组容量
  3. 创建有参构造,接收数组容量capacity,默认size为0
  4. 定义无参构造,调用有参构造,默认数组容量为10
  5. 定义获取数组容量的方法
  6. 定义获取数组内元素个数的方法
  7. 定义判断数组是否为空的方法
package array_01.base;

/**
 * @Classname Array
 * @Description TODO
 * @Date 2021/12/25 21:40
 * @Created by zhq
 */
public class Array {

    //定义数组
    private int[] data;
    //定义数组中的元素个数
    private int size;


    //构造函数,传入数组的容量capacity构造Array
    public Array(int capacity) {
        data = new int[capacity];
        size = 0;
    }

    //无参数的构造函数,默认数组的容量 capacity=10
    public Array() {
        //这里调用的是上方的Array
        //10为参数
        this(10);
    }

    //获取数组中的元素个数
    public int getSize() {
        return size;
    }

    //获取数组的容量
    public int getCapacity() {
        return data.length;
    }

    //返回数组是否为空
    public boolean isEmpty() {
        return size == 0;
    }

}

2. 添加元素

末尾添加

image-20211226105655421

步骤

  1. 判断数组的容量是否已满
  2. 添加元素
  3. 数组元素数量+1
    //数组末尾添加元素
    public void addLast(int e) {
        //当元素个数等于数组的长度时,是不能进行元素添加的,会出现错误。在此处现抛出异常
        if (size == data.length) {
            //抛出了一个传入参数有问题的异常
            throw new IllegalArgumentException("AddLast fail. Array is full.");
        }
        data[size] = e;
        size++;
    }
指定位置添加

image-20211226105637870

思路

  1. 将索引为1、2,3的元素分别向后一位平移
  2. 平移应该从后往前,从索引为3开始
  3. 添加之后,size++;

步骤

  1. 判断数组的容量是否已满
  2. 判断索引是否小于0或大于size(如果大于size,数组会排列不紧密,这样不好)
  3. 更改数组元素位置,添加位置至最后一个元素整体往后偏移
  4. 指定位置设置元素
  5. 数组元素数量+1
    // 在第index位置插入元素e
    // 根据插入位置不同,时间复杂度不同,如果从中间插入,时间复杂度为O(n/2)=O(n)
    public void add(int index, int e) {
        if (size == data.length) {
            //抛出了一个传入参数有问题的异常
            throw new IllegalArgumentException("Add fail. Array is full.");
        }
        //如果index > size   说明索引的插入位置大于数组中已存在的元素的个数,说明数据不是紧密排列的,所以要杜绝这种情况的发生
        if (index < 0 || index > size) {
            throw new IllegalArgumentException("Add fail. Require index>=0 and index<=size.");
        }
        //指定位置插入元素
        for (int i = size - 1; i >= index; i--) {
            data[i + 1] = data[i];
        }
        data[index] = e;
        size++;
    }
头部位置添加
    //向数组中的首位添加元素
    //时间复杂度O(n),其他位置元素都往后挪
    //数组头部添加元素
    public void addFirst(int e) {
        //直接调用add()方法即可实现
        this.add(0, e);
    }

3. 查询和修改元素

查询和修改
    //获取index索引位置的元素
    //O(1)
    public int get(int index) {
        if (index < 0 || index >= size)
            throw new IllegalArgumentException("Get fail. Require index>=0 and index<size.");
        return data[index];
    }

    //修改index索引位置的元素为e
    public void set(int index, int e) {
        if (index < 0 || index > size)
            throw new IllegalArgumentException("Set fail. Require index>=0 and index<=size.");
        data[index] = e;
    }
查询数组中是否包含某个元素

查找数组中是否包含某个元素e

    //查找数组中是否包含某个元素e
    //O(n)
    public boolean contains(int e) {
        for (int i = 0; i < data.length; i++) {
            if (data[i] == e) return true;
        }
        //如果循环结束,还没有找到,则说明不包含,return false即可
        return false;
    }

如果包含某个元素,则查看这个元素的索引

    //查找数组中是否包含某个元素e
    //O(n)
    public int find(int e) {
        for (int i = 0; i < data.length; i++) {
            if (data[i] == e) return i;
        }
        //如果此数组中没有元素e,则返回-1  无效索引
        return -1;
    }

4. 删除数组中元素

image-20211226111311170

思路

  1. 和往数组中某个位置添加元素思路一样
  2. 删除是让删除位置的元素的值等于其后面元素的元素,依次往前,直至最后一个元素
  3. 问题:到最后一个元素时,会出现下列问题:4号和3号内容一样
  4. 解决:其实这个问题不需要管,因为从正常操作来看,**size即指已有的元素的数量,又可以表示下个元素要插入的索引。**比如size=4,表示有四个元素,下一个元素插入的位置正好是四号位,插入新元素时,会直接把4号为原有的内容覆盖掉。所以不用在意4号位和三号为的内容一致。
删除指定位置的元素 并返回删除元素

image-20211226111530058

 	//删除某个位置的元素 并返回对应的值  1、删除某个索引位置的元素  2、删除内容为***的元素
	//O(2/n)即O(n)  因为要考虑删除的是否是第一个或最后一个位置的元素
    public int remove(int index) {
        if (index < 0 || index >= size)
            throw new IllegalArgumentException("Remove failed. Index is illegal." + "Require index>=0 and index<size.");
        int ret = data[index];
        //注意临界值是<size-1,size-1对应索引是4,我们删除一个元素之后,这个位置上的元素就没有意义了,因此不用<=size-1;
        for (int i = index; i < size - 1; i++) {
            data[i] = data[i + 1];
        }
        size--;
        return ret;
    }

    //删除第一个元素
    //O(1)
    public int removeFirst() {
        return remove(0);

    }

    //删除最后一个元素
    //O(n)
    public int removeLast() {
        //注意临界值
        return remove(size - 1);
    }
删除元素内容为*的元素(只删除一个)
    public void removeElement(int e) {
        //方法一:
        int index = -1;
        for (int i = 0; i < size; i++) {
            if (data[i] == e) index = i;
            break;
        }
        if (index > -1) remove(index);
        //方法二:
//        int index = find(e); //find(e)返回的为e的索引
//        if (index != -1) remove(index);
    }
删除多个指定位置元素

image-20211226130554825

注意:同时删除多个索引时,要先从大的索引开始删除

	//移除多个索引
    public void removeSelIndex(int... selIndex) {
        //排序:正序
        Arrays.sort(selIndex);
        //排序:反序
        for (int i = 0; i < selIndex.length / 2; i++) {
            int temp = selIndex[i];
            selIndex[i] = selIndex[selIndex.length - 1 - i];
            selIndex[selIndex.length - 1 - i] = temp;
        }
        //移除指定位置元素
        for (int i = 0; i < selIndex.length; i++) {
            //只删除在元素索引范围内的元素,超出范围的不删除
            if (selIndex[i] >= 0 && selIndex[i] < size) {
                for (int j = selIndex[i]; j < size - 1; j++) {
                    data[j] = data[j + 1];
                }
                size--;
            }
        }
    }
删除多个元素
    //删除多个元素    
	public void removeElements(int... elements) {
        for (int i = 0; i < elements.length; i++) {
            for (int j = 0; j < size; j++) {
                if (elements[i] == data[j]) {
                    remove(j);
                    j--;
                }
            }
        }
    }

5. 重写toString()方法

重写

@Override
    //添加@Override的原因是:因为toString属于Object类的方法,此时要覆盖object类的同toString方法,
    //如果toString拼写错误时,Override会检索不到Object中的方法,就会提示写法有错误
    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();
    }

Java源码

    //重写toString()方法
    @Override
    public String toString() {
        if (data == null)
            return "null";
        int iMax = size - 1;
        if (iMax == -1)
            return "[]";
        StringBuilder b = new StringBuilder();
        b.append('[');
        for (int i = 0; ; i++) {
            b.append(data[i]);
            if (i == iMax)
                return b.append(']').toString();
            b.append(", ");
        }
    }

6. 数组代码汇总

import java.util.Arrays;

/**
 * @Classname Array
 * @Description TODO
 * @Date 2021/12/25 21:40
 * @Created by zhq
 */
public class Array {

    //定义数组
    private int[] data;
    //定义数组中的元素个数
    private int size;


    //构造函数,传入数组的容量capacity构造Array
    public Array(int capacity) {
        data = new int[capacity];
        size = 0;
    }

    //无参数的构造函数,默认数组的容量 capacity=10
    public Array() {
        //这里调用的是上方的Array
        //10为参数
        this(10);
    }

    //获取数组中的元素个数
    public int getSize() {
        return size;
    }

    //获取数组的容量
    public int getCapacity() {
        return data.length;
    }

    //返回数组是否为空
    public boolean isEmpty() {
        return size == 0;
    }

    //向数组中的首位添加元素
    //时间复杂度O(n),其他位置元素都往后挪
    //数组头部添加元素
    public void addFirst(int e) {
        //直接调用add()方法即可实现
        this.add(0, e);
    }

    //数组末尾添加元素
    public void addLast(int e) {
        //当元素个数等于数组的长度时,是不能进行元素添加的,会出现错误。在此处现抛出异常
        if (size == data.length) {
            //抛出了一个传入参数有问题的异常
            throw new IllegalArgumentException("AddLast fail. Array is full.");
        }
        data[size] = e;
        size++;
    }

    // 在第index位置插入元素e
    // 根据插入位置不同,时间复杂度不同,如果从中间插入,时间复杂度为O(n/2)=O(n)
    public void add(int index, int e) {
        if (size == data.length) {
            //抛出了一个传入参数有问题的异常
            throw new IllegalArgumentException("Add fail. Array is full.");
        }
        //如果index > size   说明索引的插入位置大于数组中已存在的元素的个数,说明数据不是紧密排列的,所以要杜绝这种情况的发生
        if (index < 0 || index > size) {
            throw new IllegalArgumentException("Add fail. Require index>=0 and index<=size.");
        }
        //指定位置插入元素
        for (int i = size - 1; i >= index; i--) {
            data[i + 1] = data[i];
        }
        data[index] = e;
        size++;
    }

    //获取index索引位置的元素
    //O(1)
    public int get(int index) {
        if (index < 0 || index >= size)
            throw new IllegalArgumentException("Get fail. Require index>=0 and index<size.");
        return data[index];
    }

    //修改index索引位置的元素为e
    public void set(int index, int e) {
        if (index < 0 || index > size)
            throw new IllegalArgumentException("Set fail. Require index>=0 and index<=size.");
        data[index] = e;
    }

    //查找数组中是否包含某个元素e
    //O(n)
    public boolean contains(int e) {
        for (int i = 0; i < data.length; i++) {
            if (data[i] == e) return true;
        }
        //如果循环结束,还没有找到,则说明不包含,return false即可
        return false;
    }

    //查找数组中是否包含某个元素e
    //O(n)
    public int find(int e) {
        for (int i = 0; i < data.length; i++) {
            if (data[i] == e) return i;
        }
        //如果此数组中没有元素e,则返回-1  无效索引
        return -1;
    }

    //删除某个位置的元素 并返回index  1、删除某个索引位置的元素  2、删除内容为***的元素
    //O(2/n)即O(n)  因为要考虑删除的是否是第一个或最后一个位置的元素
    public int remove(int index) {
        if (index < 0 || index >= size)
            throw new IllegalArgumentException("Remove failed. Index is illegal." + "Require index>=0 and index<size.");
        int ret = data[index];
        //注意临界值是<size-1,size-1对应索引是4,我们删除一个元素之后,这个位置上的元素就没有意义了,因此不用<=size-1;
        for (int i = index; i < size - 1; i++) {
            data[i] = data[i + 1];
        }
        size--;
        return ret;
    }


    //移除多个索引
    public void removeSelIndex(int... selIndex) {
        //排序:正序
        Arrays.sort(selIndex);
        //排序:反序
        for (int i = 0; i < selIndex.length / 2; i++) {
            int temp = selIndex[i];
            selIndex[i] = selIndex[selIndex.length - 1 - i];
            selIndex[selIndex.length - 1 - i] = temp;
        }
        //移除指定位置元素
        for (int i = 0; i < selIndex.length; i++) {
            //只删除在元素索引范围内的元素,超出范围的不删除
            if (selIndex[i] >= 0 && selIndex[i] < size) {
                for (int j = selIndex[i]; j < size - 1; j++) {
                    data[j] = data[j + 1];
                }
                size--;
            }
        }
    }

    //删除第一个元素
    //O(1)
    public int removeFirst() {
        return remove(0);

    }

    //删除最后一个元素
    //O(n)
    public int removeLast() {
        return remove(size - 1);
    }

    //删除指定元素
    public void removeElement(int e) {
        //方法一:
        int index = -1;
        for (int i = 0; i < size; i++) {
            if (data[i] == e) index = i;
            break;
        }
        if (index > -1) remove(index);
        //方法二:
//        int index = find(e); //find(e)返回的为e的索引
//        if (index != -1) remove(index);
    }

    //删除多个元素
    public void removeElements(int... elements) {
        for (int i = 0; i < elements.length; i++) {
            for (int j = 0; j < size; j++) {
                if (elements[i] == data[j]) {
                    remove(j);
                    j--;
                }
            }
        }
    }

    //重写toString()方法
    @Override
    public String toString() {
        if (data == null)
            return "null";
        int iMax = size - 1;
        if (iMax == -1)
            return "[]";
        StringBuilder b = new StringBuilder();
        b.append('[');
        for (int i = 0; ; i++) {
            b.append(data[i]);
            if (i == iMax)
                return b.append(']').toString();
            b.append(", ");
        }
    }
}

3. 泛型使用

0. 泛型目的

  1. 让我们的数据结构可以放置“任何”数据类型

  2. 泛型限制:不可以是基本数据类型,只能是类和对象。

  3. 限制解决:java中每个基本数据类型都有对应的包装类(首字母大写就是其包装类)

  4. Boolean、Byte、Char、Short、Int、Long、Float、Double

1. 定义泛型类

public class Array<E> {
	// 定义数组
	private E[] data;
	// 定义数组中的元素个数
	// size在运用中既充当了数组中元素的个数(非长度),又可以间接充当元素的索引
	private int size;

	// 构造函数,传入数组的容量capacity构造Array
	public Array(int capacity) {
		//因为 new一个 E[]的数组会报错,所以这里new了一个Object,然后进行强制转换
		data = (E[])new Object[capacity];
		size = 0;
	}
	// 无参构造函数,默认容器的容量
	public Array() {
		this(10);
	}
}

测试

public class Main {
	public static void main(String[] args) {
		// <Integer>表示我们存放的是Integer类型的数据
	Array<Integer> arr = new Array<>(20);
	// 在数组中添加0-9这10个元素
		for (int i = 0; i < 10; i++) {
		arr.addLast(i);
	}
}

2. 泛型数组代码汇总

public class Array<E> {
    // 定义数组
    private E[] data;
    // 定义数组中的元素个数
    // size在运用中既充当了数组中元素的个数(非长度),又可以间接充当元素的索引
    private int size;

    // 构造函数,传入数组的容量capacity构造Array
    public Array(int capacity) {
        //因为 new一个 E[]的数组会报错,所以这里new了一个Object,然后进行强制转换
        data = (E[]) new Object[capacity];
        size = 0;
    }

    // 无参构造函数,默认容器的容量
    public Array() {
        this(10);
    }

    //获取数组中的元素个数
    public int getSize() {
        return size;
    }

    //获取数组的容量
    public int getCapacity() {
        return data.length;
    }

    //返回数组是否为空
    public boolean isEmpty() {
        return size == 0;
    }

    //向数组中的首位添加元素
    //时间复杂度O(n),其他位置元素都往后挪
    //数组头部添加元素
    public void addFirst(E e) {
        //直接调用add()方法即可实现
        this.add(0, e);
    }

    //数组末尾添加元素
    public void addLast(E e) {
        this.add(size,e);
    }

    // 在第index位置插入元素e
    // 根据插入位置不同,时间复杂度不同,如果从中间插入,时间复杂度为O(n/2)=O(n)
    public void add(int index, E e) {
        //如果index > size   说明索引的插入位置大于数组中已存在的元素的个数,说明数据不是紧密排列的,所以要杜绝这种情况的发生
        if (index < 0 || index > size) {
            throw new IllegalArgumentException("Add fail. 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++;
    }

    //数组重置方法   O(n)
    public void resize(int newCapacity) {
        //新建一个数组对象
        E[] newData = (E[]) new Object[newCapacity];
        for (int i = 0; i < s; i++) {
            //将之前的数组遍历到新数组中
            newData[i] = data[i];
        }
        //更换为类数组
        data = newData;
    }

    //获取index索引位置的元素
    //O(1)
    public E get(int index) {
        if (index < 0 || index >= size)
            throw new IllegalArgumentException("Get fail. Require index>=0 and index<size.");
        return data[index];
    }

    //修改index索引位置的元素为e
    public void set(int index, E e) {
        if (index < 0 || index > size)
            throw new IllegalArgumentException("Set fail. Require index>=0 and index<=size.");
        data[index] = e;
    }

    //查找数组中是否包含某个元素e
    //O(n)
    public boolean contains(E e) {
        for (int i = 0; i < data.length; i++) {
            if (data[i] == e) return true;
        }
        //如果循环结束,还没有找到,则说明不包含,return false即可
        return false;
    }

    //查找数组中是否包含某个元素e
    //O(n)
    public int find(E e) {
        for (int i = 0; i < data.length; i++) {
            if (data[i] == e) return i;
        }
        //如果此数组中没有元素e,则返回-1  无效索引
        return -1;
    }

    //删除某个位置的元素 并返回对应的值  1、删除某个索引位置的元素  2、删除内容为***的元素
    //O(2/n)即O(n)  因为要考虑删除的是否是第一个或最后一个位置的元素
    public E remove(int index) {
        if (index < 0 || index >= size)
            throw new IllegalArgumentException("Remove failed. Index is illegal." + "Require index>=0 and index<size.");
        E ret = data[index];
        //注意临界值是<size-1,size-1对应索引是4,我们删除一个元素之后,这个位置上的元素就没有意义了,因此不用<=size-1;
        for (int i = index; i < size - 1; i++) {
            data[i] = data[i + 1];
        }
        size--;
        //当元素的个数小于数组长度的1/2时,则数组长度也/2
        if(size<data.length/2) {
            resize(data.length/2);
        }
        return ret;
    }


    //移除多个索引
    public void removeSelIndex(int... selIndex) {
        //排序:正序
        Arrays.sort(selIndex);
        //排序:反序
        for (int i = 0; i < selIndex.length / 2; i++) {
            int temp = selIndex[i];
            selIndex[i] = selIndex[selIndex.length - 1 - i];
            selIndex[selIndex.length - 1 - i] = temp;
        }
        //移除指定位置元素
        for (int i = 0; i < selIndex.length; i++) {
            //只删除在元素索引范围内的元素,超出范围的不删除
            if (selIndex[i] >= 0 && selIndex[i] < size) {
                for (int j = selIndex[i]; j < size - 1; j++) {
                    data[j] = data[j + 1];
                }
                size--;
            }
        }
        //当元素的个数小于数组长度的1/2时,则数组长度也/2
        if(size<data.length/2) {
            resize(data.length/2);
        }
    }

    //删除第一个元素
    //O(1)
    public E removeFirst() {
        return remove(0);

    }

    //删除最后一个元素
    //O(n)
    public E removeLast() {
        return remove(size - 1);
    }

    //删除指定元素
    public void removeElement(E e) {
        //方法一:
        int index = -1;
        for (int i = 0; i < size; i++) {
            if (data[i] == e) index = i;
            break;
        }
        if (index > -1) remove(index);
    }

    //删除多个元素
    public void removeElements(E... elements) {
        for (int i = 0; i < elements.length; i++) {
            for (int j = 0; j < size; j++) {
                if (elements[i] == data[j]) {
                    remove(j);
                    j--;
                }
            }
        }
    }
    @Override
    //添加@Override的原因是:因为toString属于Object类的方法,此时要覆盖object类的同toString方法,
    //如果toString拼写错误时,Override会检索不到Object中的方法,就会提示写法有错误
    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();
    }
}



4. 动态数组

0. 引入问题

image-20211226141827675

**问题:**之前的数组,内部使用的还是java的静态数组。数组的数量是有限的,数组空间是自己定义的,如果开辟过大,则资源浪费,如果过小,则不够用。

**解决:**新建一个数组,并把原来数组中的元素转移到新数组中,执行完之后,newData这个变量在函数执行完之后就失效了,因为data是类变量,所以只要类存在,data就存在。而之前的空数组,在java的自动垃圾回收机制就删除了。

image-20211226141928801

1. 扩容数组

指定位置添加元素

	// 在第index位置插入元素e
    // 根据插入位置不同,时间复杂度不同,如果从中间插入,时间复杂度为O(n/2)=O(n)
    public void add(int index, E e) {
        //如果index > size   说明索引的插入位置大于数组中已存在的元素的个数,说明数据不是紧密排列的,所以要杜绝这种情况的发生
        if (index < 0 || index > size) {
            throw new IllegalArgumentException("Add fail. 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++;
    }

末尾添加元素

    //数组末尾添加元素
    public void addLast(E e) {
        this.add(size,e);
    }

2. 数组重置方法

    //数组重置方法   O(n)
    public void resize(int newCapacity) {
        //新建一个数组对象
        E[] newData = (E[]) new Object[newCapacity];
        for (int i = 0; i < size; i++) {
            //将之前的数组遍历到新数组中
            newData[i] = data[i];
        }
        //更换为类数组
        data = newData;
    }

3. 缩容数组

根据指定索引删除元素

    //删除某个位置的元素 并返回对应的值
    //O(2/n)即O(n)  因为要考虑删除的是否是第一个或最后一个位置的元素
    public E remove(int index) {
        if (index < 0 || index >= size)
            throw new IllegalArgumentException("Remove failed. Index is illegal." + "Require index>=0 and index<size.");
        E ret = data[index];
        //注意临界值是<size-1,size-1对应索引是4,我们删除一个元素之后,这个位置上的元素就没有意义了,因此不用<=size-1;
        for (int i = index; i < size - 1; i++) {
            data[i] = data[i + 1];
        }
        size--;
        //当元素的个数小于数组长度的1/2时,则数组长度也/2
        if(size<data.length/2) {
            resize(data.length/2);
        }
        return ret;
    }

批量删除索引元素

    //移除多个索引
    public void removeSelIndex(int... selIndex) {
        //排序:正序
        Arrays.sort(selIndex);
        //排序:反序
        for (int i = 0; i < selIndex.length / 2; i++) {
            int temp = selIndex[i];
            selIndex[i] = selIndex[selIndex.length - 1 - i];
            selIndex[selIndex.length - 1 - i] = temp;
        }
        //移除指定位置元素
        for (int i = 0; i < selIndex.length; i++) {
            //只删除在元素索引范围内的元素,超出范围的不删除
            if (selIndex[i] >= 0 && selIndex[i] < size) {
                for (int j = selIndex[i]; j < size - 1; j++) {
                    data[j] = data[j + 1];
                }
                size--;
            }
        }
        //当元素的个数小于数组长度的1/2时,则数组长度也/2
        if(size<data.length/2) {
            resize(data.length/2);
        }
    }

5 .时间复杂度分析

0. 数组复杂度分析

image-20211226145734640

  1. n是数组nums元素的个数

  2. O(n)表示的是时间复杂度和n是线性相关

  3. O 是渐进复杂度,描述n趋近于无穷的情况

  4. 线性方程:忽略常数,实际时间T=c1*n+c2。c1表示一个元素执行的时间,c2表示除去数组元素外,其他数据的执行时间

    1. T=2*n+2 O(n )
    2. T=2000*n+1000 O(n)
    3. T=1 * n *n+0 O(n^2)
      1. 当n=10时,比较第二和第三,执行时间n^2<n,但是当趋于无穷大时,就不成立了
    4. T=2* n * n+300n+100 O(n^2)
  5. 数组操作的时间复杂度

    1. 增加: O(n)
    2. 删除: O(n)
    3. 修改: 已知索引(O1),未知索引 O(n)
    4. 查询:已知索引(O1),未知索引 O(n)
    5. 増和删的操作时,如果只对最后一个数操作时,时间复杂度为O(1)。但是我们说他是O(n)级别的操作,是因为有resize,resize需要把整个元素全都复制一遍。
    6. resize(扩容),假如每次进行添加操作时,我们都进行扩容,那么他们复杂度就是O(n)
    7. 但是resize的使用频率很小,比如我向容器为10的数组中添加元素,添加十次之后,才进行一次扩容,再添加10此,在进行一次扩容。所有遇到这种情况,我们往最坏的方面考虑是不合适的。考虑到这个问题,我们就可以想到使用均摊复杂度

1. 均摊复杂度

  1. 一个相对比较耗时的操作,如果我们能保证他不会每次都触发的话,这个相对复杂的操作的操作时间是可以分配到整个操作中来的。

    1. 假如一个数组容量为8,我进行9次addLast()操作时,才会调用一次resize()扩容方法,每调用一次resize()扩容方法,那么操作数+8。
    2. 我们一共进行了17次基本操作。平均每次addLast(),执行了两次基本操作。 17/8约等于2.
    3. 通过这样均摊计算,时间复杂度是O(1)的,和数组中有多少个元素无关。
  2. 针对这个例子,我们采用均摊计算,比计算最坏情况更有意义。

  3. 同理,removeLast()的时间复杂度也是O(1)级别的。

2. 复杂度震荡

问题:

假如一个数组的容量为8,此时数组已满,我再调用addLast()方法,此时会调用一次resize()的扩容方法,此次操作复杂度为O(n),下一步操作,我调用removeLast()操作。此时会调用一次resize()的缩容方法,此次操作复杂度又是O(n),如此循环。

问题原因:

removeLast()时,resize()的缩容操作过急(Eager)

解决:

lazy方式

扩容操作后,我们的数组容量为16。我们修改缩容的resize()方法,当数组内元素的数量占数组容量的1/4时,我们再将数组的容量缩小至原来的1/2.这样就避免了复杂度震荡这种情况的发生.

		//当元素的个数小于数组长度的1/2时,则数组长度也1/2
		if(size<data.length/2) {
			resize(data.length/2);
		}
       //修改
       if(size<data.length/4&&data.lentgth/4!=0) {
			resize(data.length/2);
		}

算法

1. 力扣26:删除排序数组中的重复项

26. 删除有序数组中的重复项 - 力扣(LeetCode) (leetcode-cn.com)

我的答案

public class RemoveRepeat_26 {
    public static void main(String[] args) {
        int[] nums = {1,1,1,2};
        int length = removeDuplicates(nums);
        for (int i = 0; i < length; i++) {
            System.out.println(nums[i]);
        }
    }

    public static int removeDuplicates(int[] nums) {
        int pointer = 0;
        int var = 0;
        int length = 1;
        int repeat = 0;
        for (int i = pointer + 1; i < nums.length-repeat; i++) {
            //判断元素是否重复
            if (nums[pointer] == nums[i]) {
                //重复元素的个数
                var++;
                if (var == nums.length - 1)
                    //全是一个数
                    return 1;
            } else {
                length++;
                for (int j = pointer; j < nums.length - var; j++) {
                    nums[j] = nums[j + var];
                }
                pointer++;
                repeat+=var;
                i = pointer;
                var = 0;
            }
        }
        return length;
    }
}

2. 力扣189:旋转数组

189. 轮转数组 - 力扣(LeetCode) (leetcode-cn.com)

我的答案

public class RotateArray {
    public static void main(String[] args) {
        int[] nums = {1, 2, 3, 4, 5, 6, 7};
        rotateTwo(nums, 3);
        for (int i = 0; i < nums.length; i++) {
            System.out.println(nums[i]);
        }
    }
    //方法一:一个一个移动,超时
    public static void rotateOne(int[] nums, int k) {
        if (k >= nums.length) k = k % nums.length;
        while (k > 0) {
            int val = nums[nums.length - 1];
            for (int i = nums.length - 1; i > 0; i--) {
                nums[i] = nums[i - 1];
            }
            nums[0] = val;
            k--;
        }
    }
    //方法二:旋转后,找对应规律。成功。
    public static void rotateTwo(int[] nums, int k) {
        if (k >= nums.length) k = k % nums.length;
        int[] brr = new int[nums.length];
        for (int i = 0; i < nums.length; i++) {
            brr[i] = nums[i];
        }
        for (int i = 0; i < nums.length; i++) {
            nums[i] = brr[(i + nums.length - k) % nums.length];
        }
    }
}

3. 力扣217:存在重复元素

217. 存在重复元素 - 力扣(LeetCode) (leetcode-cn.com)

我的答案

public class JudgeRepeat_217 {
    public static void main(String[] args) {
        int[] nums = {1, 2, 3, 4};
        System.out.println(containsDuplicate(nums));
    }

    public static boolean containsDuplicate(int[] nums) {
        if (nums.length==0){
            return false;
        }
        Arrays.sort(nums);
        for (int i = 0; i < nums.length - 1; i++) {
            if (nums[i] == nums[i + 1]) {
                return true;
            }
        }
        return false;
    }
}

stream流

    public static boolean containsDuplicateTwo(int[] nums) {
        return Arrays.asList(nums).stream().distinct().count() < nums.length;
    }

使用set

    public boolean containsDuplicate(int[] nums) {
        Set<Integer> set = new HashSet<Integer>();
        for (int x : nums) {
            if (!set.add(x)) {
                return true;
            }
        }
        return false;
    }

4. 力扣136:只出现一次的数字

只出现一次的数字 - 只出现一次的数字 - 力扣(LeetCode) (leetcode-cn.com)

我的答案

public class OnlyAppearOneNum_136 {
    public static void main(String[] args) {
        int[] nums = {4, 1, 2, 1, 2};
        singleNumber(nums);
    }

    public static int singleNumber(int[] nums) {
        Arrays.sort(nums);
        for (int i = 0; i < nums.length - 1; i += 2) {
            if (nums[i] != nums[i + 1]) {
                return nums[i];
            }
        }
        if (nums.length % 2 != 0) {
            return nums[nums.length - 1];
        }
        return -1;
    }
}

异或运算

异或运算有以下三个性质。

  1. 任何数和 0做异或运算,结果仍然是原来的数,即 a ⊕ 0=a。
  2. 任何数和其自身做异或运算,结果是 0,即 a⊕a=0。
  3. 异或运算满足交换律和结合律,即 a⊕b⊕a=b⊕a⊕a=b⊕(a⊕a)=b⊕0=b。

4⊕1⊕2⊕1⊕2

=100⊕001⊕010⊕001⊕010

=101⊕010⊕001⊕010

=101⊕001⊕010

=100⊕010

=100

=4

    public static int singleNumberTwo(int[] nums) {
        int single = 0;
        for (int num : nums) {
            single ^= num;
        }
        return single;
    }
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

See you !

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值