基本概念
数组(Array)是一种线性表(Liner List)数据结构,它用一组连续的内存空间,来存储一组具有相同类型的数据。
特点
连续的内存空间和相同的数据类型,因为这两个限制,数组拥有随机访问的特性,根据索引访问元素的时间复杂度是O(1)。
但是这两个限制让数组的很多操作变得非常低效,比如数组的删除和插入操作,为了保证连续性,就需要大量的数据复制和搬运操作。
寻址公式:
a[i]_address = base_address + i * data_type_size
数组的java语言实现<java.util.ArrayList>
public class ArrayList<E> extends AbstractList<E>
implements List<E>, RandomAccess, Cloneable, Serializable
{
/**
* The default capacity for new ArrayLists.
*/
private static final int DEFAULT_CAPACITY = 10;
/**
* Where the data is stored.
*/
private transient E[] data;
/**
* Construct a new ArrayList with the supplied initial capacity.
*
* @param capacity initial capacity of this ArrayList
* @throws IllegalArgumentException if capacity is negative
*/
public ArrayList(int capacity)
{
// Must explicitly check, to get correct exception.
if (capacity < 0)
throw new IllegalArgumentException();
data = (E[]) new Object[capacity];
}
/**
* Construct a new ArrayList with the default capacity (16).
* 使用默认容量构造新的ArrayList(16)
*/
public ArrayList()
{
this(DEFAULT_CAPACITY);
}
/**
* Guarantees that this list will have at least enough capacity to
* hold minCapacity elements. This implementation will grow the list to
* max(current * 2, minCapacity) if (minCapacity > current). The JCL says
* explictly that "this method increases its capacity to minCap", while
* the JDK 1.3 online docs specify that the list will grow to at l east the
* size specified.
* "保证数组有足够的容量,如果不够,需要进行扩容,将容量扩大为Math.max(current * 2, minCapacity)"
* "创建一个新的数组,然后也会进行复制搬运操作"
* @param minCapacity the minimum guaranteed capacity
*/
public void ensureCapacity(int minCapacity)
{
int current = data.length;
if (minCapacity > current)
{
E[] newData = (E[]) new Object[Math.max(current * 2, minCapacity)];
System.arraycopy(data, 0, newData, 0, size);
data = newData;
}
}
/**
* Returns true iff element is in this ArrayList.
*
* @param e the element whose inclusion in the List is being tested
* @return true if the list contains e
*/
public boolean contains(Object e)
{
return indexOf(e) != -1;
}
/**
* Returns the lowest index at which element appears in this List, or
* -1 if it does not appear.
* "看来查询一个数组是否包含某一个值,也就是执行一次O(n)的for循环遍历"
* @param e the element whose inclusion in the List is being tested
* @return the index where e was found
*/
public int indexOf(Object e)
{
for (int i = 0; i < size; i++)
if (equals(e, data[i]))
return i;
return -1;
}
/**
* Retrieves the element at the user-supplied index.
*
* @param index the index of the element we are fetching
* @throws IndexOutOfBoundsException if index < 0 || index >= size()
*/
public E get(int index)
{
"//首先判断索引是否越界"
checkBoundExclusive(index);
return data[index];
}
/**
* Sets the element at the specified index. The new element, e,
* can be an object of any type or null.
*
* @param index the index at which the element is being set
* @param e the element to be set
* @return the element previously at the specified index
* @throws IndexOutOfBoundsException if index < 0 || index >= 0
*/
public E set(int index, E e)
{
"//首先判断索引是否越界"
checkBoundExclusive(index);
E result = data[index];
data[index] = e;
return result;
}
/**
* Appends the supplied element to the end of this list.
* The element, e, can be an object of any type or null.
* "添加元素到数组的末尾。如果当前的size等于数组的长度,就需要进行扩容(复制和搬运)操作"
* Tips: i = 2 ; i++ = 2 ++i = 3 而 i = 3
* @param e the element to be appended to this list
* @return true, the add will always succeed
*/
public boolean add(E e)
{
modCount++;
if (size == data.length)
ensureCapacity(size + 1);
data[size++] = e;
return true;
}
/**
* Adds the supplied element at the specified index, shifting all
* elements currently at that index or higher one to the right.
* The element, e, can be an object of any type or null.
* "在指定索引处添加提供的元素,将当前位于该索引处或更高索引处的所有元素向右移动"
* @param index the index at which the element is being added
* @param e the item being added
* @throws IndexOutOfBoundsException if index < 0 || index > size()
*/
public void add(int index, E e)
{
checkBoundInclusive(index);
modCount++;
if (size == data.length)
ensureCapacity(size + 1);
if (index != size)
"index并不是末尾的索引,需要将从index到size的元素复制搬运到index+1的位置,元素的个数即长度为size - index"
"时间复杂度为O(n)"
System.arraycopy(data, index, data, index + 1, size - index);
data[index] = e;
size++;
}
/**
* Removes the element at the user-supplied index.
* "删除指定数组中指定索引的元素"。
* @param index the index of the element to be removed
* @return the removed Object
* @throws IndexOutOfBoundsException if index < 0 || index >= size()
*/
public E remove(int index)
{
"//检查索引是否越界"
checkBoundExclusive(index);
E r = data[index];
modCount++;
if (index != --size)
"index指定的元素不是末尾元素。需要将index + 1到size的元素复制搬运到index的位置,元素的个数即长度为size - index"
"时间复杂度为O(n)"
System.arraycopy(data, index + 1, data, index, size - index);
// Aid for garbage collection by releasing this pointer.
data[size] = null;
return r;
}
实战题目
移动零LeetCode链接
给定一个数组 nums,编写一个函数将所有 0 移动到数组的末尾,同时保持非零元素的相对顺序。
示例:
输入: [0,1,0,3,12] 输出: [1,3,12,0,0]
说明: 必须在原数组上操作,不能拷贝额外的数组。 尽量减少操作次数。
Tips: 在对数组进行操作的时候,尽最大可能不要对数组进行插入和删除操作。从数组源码上看,插入和删除操作内部会执行复制搬运操作,时间复杂度是O(n)。
方法1
用到了数组的删除方法,内部时间复杂度是O(n),外部又有for遍历时间复杂度为O(n),所以此方案的时间复杂度是O(n^2)
class Solution:
def moveZeroes(self, nums: List[int]) -> List[int]:
deleteNum = 0
for num in nums[:]:
if num == 0:
//用到了数组的删除方法,时间复杂度是O(n)
nums.remove(num)
deleteNum += 1
for i in range(deleteNum):
nums.append(0)
return nums
方法2
通过双指针法,空间换取时间的方法,细分的话,我起了一个名字叫同向双指针法,当两个指针index与i出现不相等时候,必然是需要做特殊处理的时机,因为他们是从同一个起点,同一个方向出发的。
index记录下一个非零的索引或者说个数。当遇到元素为零的时候,待下一次遍历到非零的元素时,index和i自然不会相等,这时候将非零元素赋值给index,并index+1,将i位置的元素赋值为0,相当于交换。时间复杂度为O(1)。这样这个算法时间复杂度就为O(n)。
def moveZeroes(self, nums: List[int]) -> List[int]:
"""
数组尽量不要做拷贝操作,使用一个删除操作,删除操作本身会拷贝数据
"""
index = 0
for i in range(len(nums)):
if nums[i] != 0:
nums[index] = nums[i]
if i != index:
//这个条件如何很快想得通? 其实这也是相当于一种交换
nums[i] = 0
index += 1
return nums