基本概念
线性表
存储的角度
顺序型就是将数据存放在一段固定的区间,访问元素效率高,删除和增加元素代价大(效率低)
扩容只能整体迁移
链表型,元素之间通过地址依次连接,访问的时候从头开始逐步向后遍历,查找效率低
删除和增加元素方便(无需扩容)
访问限制的角度
栈和队列称为访问受限的线性表,插入和删除存在限制,只能在固定位置进行
Hash内部存储数据是数组,访问是通过映射来实现
线性表常见操作:初始化、求表长、增删改查等
扩容角度
扩容策略
第一种:每次扩充增加固定数目的存储位置(线性增长)
特点:节省空间,但操作次数多
第二种:每次扩充容量加倍
特点:减少扩充操作次数(以空间换时间)
数组存储元素特征
1)数组默认初始化为0
2)初始化本质是覆盖已有的值
3)初始化必须从前向后的连续空间 初始化,不可以出现空缺的情况
数组基本操作
数组创建和初始化
int[] nums = {2,5,0,4,6,-10};
查找一个元素
数组由于查找效率高,所以很多题目本质是查找问题需要利用数组来解决问题
包括很多算法也是为了提高查找效率,例如:二分查找、二叉树、红黑树、Hash和堆等
也有很多算法问题本质是查找问题,例如:滑动窗口问题、回溯问题、动态规划问题等
//查找时如果相等或者当前位置元素比目标值更大就停下了
public static int findByElement(int[] arr, int size, int key) {
for (int i = 0; i < size; i++) {
if (arr[i] >= key)
return i;
}
return -1;
}
增加一个元素
数组的增加和删除元素是基本操作,但容易在处理下标与边界值的关系出现问题=
例题:将给定的元素插入到有序数组的对应位置中
public static int addByElementSequence(int[] arr, int size, int element) {
//size和arr.length都表示元素的数量,都是从1开始编号
if (size >= arr.length)
throw new IllegalArgumentException("Add failed. Array is full.");
//如果遍历完整个数组都没有找到插入位置,说明直接在数组最后插入
int index = size;
//找到新元素的插入位置
for (int i = 0;i<size;i++){
if (element < arr[i]){
index = i;
break;
}
}
//找到插入位置后开始插入,整体后移挪出位置
//凡是大于index的数组元素都往后移动一位
for (int i =size;i>index;i--){
arr[i] = arr[i-1];
}
arr[index] = element;//插入数据
return index;
}
删除一个元素
删除的话就不能实现一边从后向前移动一边查找,元素可能不存在
只能从前往后遍历
//如果删除失败则返回数组原来长度
public static int removeByElement(int[] arr, int size, int key) {
int index = -1;
for (int i = 0; i < size; i++) {
if (arr[i] == key) {
index = i;
break;
}
}
if (index != -1) {
for (int i = index + 1; i < size; i++)
arr[i - 1] = arr[i];
size--;
}
return size;
}
单调数组问题
leetcode896
思路分析:
单调有递增和递减两种情况,所以可以在一次遍历中同时检查单调递增和单调递减
如果在遍历违背了单调递增或递减的顺序,将相应的布尔标志设为false
public boolean isMonotonic(int[] nums) {
//如果遍历完到最后仍未true说明数组单调
boolean inc = true;
boolean dec = true;
for(int i =0;i<nums.length-1;i++){
if(nums[i]>nums[i+1]){
inc = false;
}else if(nums[i] < nums[i+1]){
dec = false;
}
}
return (inc || dec);
}
判断单调性可以在查找的过程中利用到二分查找来提高查找效率
思路分析::
1. **初始化阶段**:
n是数组 nums的长度。 left`和 right 是我们要在其中搜索的数组段的开始和结束索引 开始时,left` 为0,`right` 为 `n-1`。
ans` 被初始化为 `n`,它是一个备选的插入位置。如果 `target` 大于数组中的所有元素, ans将保持为n,表示 target 应该被插入到数组的末尾。2. 二分查找循环:
当 left 不大于 right 时,说明还没有到循环位置,则循环继续。
计算中间位置 mid。这里,我们使用位操作 >> 1 来代替除以2,因为位操作更快。 (right - left) >> 1 为数组段长度的一半,加上 left 就得到了中间位置。
3. 比较阶段:
3.1如果 target 小于或等于 nums[mid],这意味着目标值应该在数组的左半段或恰好在 mid 位置。
3.2我们更新 `ans` 为 `mid`,因为这可能是 `target` 的位置或 `target` 应该插入的位置。
3.3然后,我们更新 `right` 为 `mid - 1`,因为我们知道 `target` 不可能在 `mid` 的右侧。
3.4否则,`target` 大于 `nums[mid]`,这意味着 `target` 应该在数组的右半段。
3.5所以,我们只更新 `left` 为 `mid + 1`。
4. 返回结果:
4.1当循环结束时,`ans` 将包含 `target` 在数组中的位置,或者如果 `target` 不在数组中,它将包含 `target` 应该插入的位置。
4.2最后,`ans` 被返回
//二分查找
public static int searchInsert(int[] nums, int target) {
int n = nums.length;
int left = 0, right = n - 1, ans = n;
while (left <= right) {
int mid = ((right - left) >> 1) + left;
if (target <= nums[mid]) {
ans = mid;
right = mid - 1;
} else {
left = mid + 1;
}
}
return ans;
}
数组合并
leetcode88
题目:合并两个有序数组
解决方案:
方法一:先将B直接合并到A后面,然后再对A排序(没有技术含量)
public static void merge1(int[] nums1, int nums1_len, int[] nums2, int nums2_len) {
for (int i = 0; i < nums2_len; ++i) {
nums1[nums1_len + i] = nums2[i];
}
Arrays.sort(nums1);
}
方法二:借助一个新数组C,将选择好的放入到新数组1中去,时间复杂度O(n)
方法三:从后向前插入,由于A和B元素为有序数组,排序最后位置就是A和B元素最大,每次找最大从后向前填(推荐)
public void merge(int[] nums1, int m, int[] nums2, int n) {
//数组从0开始遍历
int length = m+n-1;
int index1 = m-1;
int index2 = n-1;
//从后往前填入到num1中
while(index1>=0 && index2>=0){
if(nums1[index1]>=nums2[index2]){
nums1[length--] = nums1[index1--];
}else{
nums1[length--] = nums2[index2--];
}
}
//nums2有剩余
while(index2>=0){
nums1[length--] = nums2[index2--];
}
}