一、什么是数组?
- 数组由相同类型的元素(element)的集合所组成的结构
- 分配一块连续的内存来存储元素
- 利用元素的索引(index)可以计算出该元素对应的储存地址
二、数组Array的特性
- 在内存中为连续空间,定址公式: addr(curElem) = addr(intialElem) +sizeof(curElem) * index
- 存储在数组中的元素是相同类型的
- 通过index获取数组元素的时间复杂度为O(1)
三、ArrayList的实现
- Java中的声明: ArrayList list = new ArrayList();
- 基本操作
- 利用数组作为存储
- 初始化时需要指定ArrayList的容量
- 记得边界检查
- 当达到数组容量时再添加新元素时,需要resize操作对底层数组进行扩容
附上实现代码:
public class ArrayList {
private int capacity;
private int size;
private int[] data;
public ArrayList(int capacity){
this.capacity = capacity;
this.size = 0;
this.data = new int[capacity];
}
public int get(int index){
if(index<0 || index>size){
//throw Exception
}
return data[index];
}
public void set(int index,int value){
if(index<0 || index>size){
//throw Exception
}
data[index] = value;
}
public void add(int index , int value){
if(index < 0 || index > size){
//throw Exception
}
if(size == capacity){
resize();
}
size++;
for(int i = size -1; i>= index+1;i--){
data[i] = data[i-1];
}
data[index] = value;
}
public void remove(int index){
if(index < 0 || index > size){
//throw Exception
}
size--;
for(int i = index;i<size;i++){
data[i] = data[i+1];
}
}
private void resize(){
capacity*=2;
int[] new_data = new int[capacity];
for(int i = 0;i<size;i++){
new_data[i] = data[i];
}
data = new_data;
}
四、例题
1、两数之和
思路1 : 暴力遍历,遍历取一个数,计算它与其它数字之和,遍历全部情况得到想要的结果对。时间复杂度为: O(n2)
public int[] twoSum(int[] nums,int target) {
int[] result = new int[2];
if(nums.length < 2) return result;
for(int i = 0;i<(nums.length-1);i++){
for(int j = i+1 ; j<nums.length;j++){
if(nums[i]+nums[j] == target){
result[0] = nums[i];
result[1] = nums[j];
}
}
}
return result;
}
思路2 :排序 + 两根指针
public int[] twoSum(int[] nums,int target) {
int[] result = new int[2];
Arrays.sort(nums);
int start = 0, end = nums.length -1;
while(start < end){
if(nums[start] + nums[end] == target){
result[0] = nums[start];
result[1] = nums[end];
return result;
}
if(nums[start] + nums[end] > target){
end--;
}
if(nums[start] + nums[end] < target){
start++;
}
}
return result;
}
-
核心想法:如果现在两根指针所指元素之和大于目标值,则表明现在两数之和过大,应使end指针指向更小的数,即索引减小(end–), 反之则表明现在两数之和过小,应使start指针指向更大的数,即索引增加(start++)
-
通过对数组排序与两根指针组合,减少无意义的遍历
-
时间复杂度分析:排序: O(nlogn), 两根指针算法:O(n)
-
时间复杂度: O(nlogn) + O(n) = O(nlogn)
2、三数之和
思路1 : 当然可以用暴力遍历求解,时间复杂度为O(n3)
思路2 : 排序 + 两根指针算法求解O(n2)
public int[] threeSum(int[] nums, int target) {
int[] result = new int[3];
if(nums.length < 3) return nums;
Arrays.sort(nums);
for(int i =0; i < nums.length-2;i++){
int start = i+1,end = nums.length-1,new_target = target-nums[i];
while (start < end) {
if (nums[start] + nums[end] == new_target) {
result[0] = nums[start];
result[1] = nums[end];
result[2] = nums[i];
return result;
}
if (nums[start] + nums[end] > new_target) {
end--;
}
if (nums[start] + nums[end] < new_target) {
start++;
}
}
}
return result;
}
- 遍历第一个数字num1,看看另外两数之和是否能满足target – num1,这就转化为两数之和的问题
- 时间复杂度:O(nlogn) + n * O(n) =O(n2)
3、K-Sum解法总结
- 排序
- 尝试遍历第一个数,将问题转化为 k–1 Sum
- 2-Sum: O(nlogn) + O(n) = O(nlogn)
- 3-Sum: O(nlogn) + O(n2) = O(n2)
- 4-Sum: O(nlogn) + O(n3) = O(n3)
- k-Sum: O(nlogn) + O(nk-1) = O(nk-1)
四数之和举例:
public int[] fourSum(int[] nums, int target) {
int[] result = new int[4];
if(nums.length < 4) return nums;
Arrays.sort(nums);
for(int i =0; i < nums.length-2;i++){
for(int j = i+1 ; j<nums.length-3;j++){
int start = j+1,end = nums.length-1,new_target = target-nums[i]-nums[j];
while (start < end) {
if (nums[start] + nums[end] == new_target) {
result[0] = nums[start];
result[1] = nums[end];
result[2] = nums[i];
result[3] = nums[j];
return result;
}
if (nums[start] + nums[end] > new_target) {
end--;
}
if (nums[start] + nums[end] < new_target) {
start++;
}
}
}
}
return result;
}
4、反转数组
给定一个数组,反转数组中所有的数字,例如:Input:{1,2,3,4,5},Output:{5,4,3,2,1}
public void reverseArray(int[] nums) {
int start = 0 , end = nums.length - 1;
while(start < end){
int temp = nums[start];
nums[start] = nums[end];
nums[end] = temp;
start++;
end--;
}
}
- 思路:双指针,首尾交换,时间复杂度O(n)
5、奇数偶数排序
给定一组整数,对它们进行排序,以便所有奇数整数在偶数整数之前出现。 元素的顺序可以改变。排序的奇数和偶数的顺序无关紧要。例如:Input:{4,3,5,2,1,11,0},Output:{9,3,5,11,0,2,4}
public void oddEvenSort(int[] nums) {
int first = 0 , second = nums.length - 1;
while(first < second){
// 奇数的话,第一根指针就往后面移动一位,直到为偶数停下来
while(first < second && nums[first] % 2==1){
first++;
}
// 偶数的话,第二根指针就往前移动一位,直到为奇数停下
while(first < second && nums[first] % 2==0){
second--;
}
if(first < second){
int temp = nums[first];
nums[first] = nums[second];
nums[second] = temp;
first++;
second--;
}
}
}
- 思路:双指针,在一定条件下进行首尾交换,时间复杂度O(n)
6、合并两个有序数组
给定两个有序整数数组 nums1 和 nums2,请按递增顺序将它们合并到一个排序数组中。例如:Input: {1, 3, 5}, {2, 4, 6},Output: {1, 2, 3, 4, 5, 6}
思路1:对两个数组合并O(n),合并后排序O(nlogn),时间复杂度:O(n)+O(nlogn)=0(nlogn)
思路2:双指针 O(n)
public int[] merge(int[] arr1,int[] arr2){
int[] result = new int[arr1.length + arr2.length];
int index=0,index1=0,index2=0;
while(index1 < arr1.length && index2 < arr2.length){
if(arr1[index1]<arr2[index2]){
result[index++] = arr1[index1++];
}else{
result[index++] = arr2[index2++];
}
}
for(int i = index1;i<arr1.length;i++){
result[index++] = arr1[i];
}
for(int i = index2;i<arr2.length;i++){
result[index++] = arr2[i];
}
return result;
}