概念
- 集合:一堆元素的组合,元素没有顺序、无关类型的存储
- 列表:在集合的基础上形成,元素长度可变,顺序排列存储。数组、链表、栈、队列皆属于特殊的列表
- 数组:在列表的基础上,java:限制元素为同一类型。同时,其元素之间的内存空间地址是连续的,因此我们可以借助第一个元素的地址,就能够访问到数组中任一个位置的元素的值,此概念即为:索引。
数组的特点
查找、修改元素快
- 如上一部分的内容所示,我们借助索引,只需要进行简单的加法运算获得该地址即可。
//假设ans数组的第一个元素ans[0]内存地址为0x110
//该数组是int型数组,则每个元素占4个字节
int[] ans = new int[10];
//则ans[3]的地址为:0x122
插入元素慢
- 由于数组中的元素是存放在一组连续的地址空间,我们若想在元素A和元素B之间插入元素C则需将元素B及其之后的元素全部向右移出1格,给元素C腾出空间。
删除元素慢
- 若是我们删除的元素不是数组中最后一个元素,那么我们需要将该元素后面的所有元素覆盖前面1个的元素,让元素所对应的内存地址是连续的。
双指针
虽然数组的数据结构很简单,但有关数组的算法非常多,比如各种排序算法。本文只分析双指针。
运用双指针可以解决数组的一小部分问题,比如:反转数组,二分查找等。
- 双指针模板
int left,right;//用来存储数组索引,代表左右指针(没错,模板就两个变量)
双指针初始化
- 左指针设为0,指向第一个元素,右指针设为数组长度,或者数组长度-1指向尾部元素。两个指针根据算法向中间逼近。
- 左指针设为0,指向第一个元素。右指针设为1,指向第二个元素,向同一方向移动,此种类型的双指针用于右指针移动的次数或者说机会一定比左指针多。
- 左右指针均设为0,指向第一个元素,向同一方向移动,不过右指针(快指针)每次移动的步长比左指针(慢指针)多,此类型的双指针称为快慢指针。
以上3种是常用的双指针类型,还有许多其他的方式初始化双指针,根据不同的情境选用不同的双指针。
二分查找
接下来就运用双指针实现二分查找。
注意,实现二分查找需要数组本身是有序数组。
来自leetcode搜索插入位置
- 给定一个排序数组和一个目标值,在数组中找到目标值,并返回其索引。如果目标值不存在于数组中,返回它将会被按顺序插入的位置。
输入: nums = [1,3,5,6], target = 5
输出: 2
class Solution {
public int searchInsert(int[] nums, int target) {
return erfen(nums,target,0,nums.length);
}
//left为左指针,right为右指针
public int erfen(int[] nums,int target,int left,int right) {
//当左右指针相等或位置互换,结束递归
if(left >= right) { return left; }
//二分查找的核心语句就在于此句,通过左右指针获取区间中心位置
int mid = (left + right)/2;
//判断该位置上的值是否目标值
if(nums[mid] == target) {
return mid;
}
else if(nums[mid] < target) {
//若该位置值比目标小,说明我们要找的目标值一定位于区间中心与右指针之间
return erfen(nums, target, mid+1, right);
}
//若该位置值比目标大,说明我们要找的目标值一定位于区间中心与左指针之间
else {
return erfen(nums, target, left, mid);
}
}
}
二维数组
本文之前的数组都只是一维数组。
二维数组是指一维数组中的每一个元素又都是一个一维数组。一维数组我们可以将之看成一条线,而二维数组我们将之看成一个矩阵,因此我们可以引入坐标的概念。
- 二维数组
int[][] mat = {{1,2,3},{4,5,6},{7,8,9}};
[ 1 , 2 , 3 ]
[ 4 , 5 , 6 ]
[ 7 , 8 , 9 ]
其中元素值为5的坐标为(1,1)。
给你一幅由 N × N 矩阵表示的图像,其中每个像素的大小为 4 字节。请你设计一种算法,将图像旋转 90 度。来自leetcode旋转矩阵
源图像:
[ 1 , 2 , 3 ]
[ 4 , 5 , 6 ]
[ 7 , 8 , 9 ]
反转后:
[ 7 , 4 , 1 ]
[ 8 , 5 , 2 ]
[ 9 , 6 , 3 ]
class Solution {
public void rotate(int[][] matrix) {
//首先上下翻转
for(int i=0;i<matrix.length/2;i++){
int j = matrix.length - 1 -i;
for(int k=0;k<matrix[0].length;k++){
int num = matrix[i][k];
matrix[i][k] = matrix[j][k];
matrix[j][k] = num;
}
}
//[ 7 , 8 , 9 ]
//[ 4 , 5 , 6 ]
//[ 1 , 2 , 3 ]
//沿对角线交换(8和4交换,9和1交换,6和2交换)
for(int i=0;i<matrix.length-1;i++){
for(int j=i+1;j<matrix[0].length;j++){
int num = matrix[i][j];
matrix[i][j] = matrix[j][i];
matrix[j][i] = num;
}
}
}
}
-
心得:解决二维数组问题时,其实理论上的解决方案较容易想到和理解,但实际上手代码时,又会有许多问题显现。
其中出现最多的问题就是下标越界问题。
出现这个问题后,我们可以借助如idea等集成环境进行查错(不推荐)。
或者我们自己根据案例进行推导,但根据案例推导是件让人心烦意乱的事情,因为你需要记住的各种变量的变化实在太多,因此案例推导若进行的不顺利,我建议应立即重新梳理一下思路,然后再进行推导,如此反复方成大道(理论能力和代码能力)。 -
补充:如何将坐标当作存储进一维数组,请查阅笔者的队列 BFS的补充部分