数组理论基础
数组是存放在连续内存空间上的相同类型数据的集合,可以方便地通过下标索引的方式获取到下标所对应的数据
要点:
- 数组下标都是从0开始的
- 数组内存空间的地址是连续的
- 增删元素时,需移动其他元素的地址
- 元素的地址只能覆盖,不能删除
| 概念 | 数组 | 链表 |
|---|---|---|
| 内存分配 | 连续内存 | 非连续内存,节点通过指针连接 |
| 访问速度 | 快速,通过索引直接访问 | 较慢,需要从头遍历到目标节点 |
| 插入/删除 | 较慢,需要移动元素 | 快速,只需改变指针 |
| 内存效率 | 效率较高,无需额外指针存储 | 效率较低,每个节点需要额外指针 |
704. 二分查找
解题方法: 二分查找
特征: 有序数组且数组中无重复元素(确保查找下标值唯一)
重难点: 区间的定义就是不变量,要在二分查找的过程中,保持不变量,即:在while循环中坚持根据查找区间的定义来做边界处理,就是循环不变量规则。
实现方式: 1、左闭右闭,定义target在[left, right]区间;2、左闭右开,定义target在[left, right)区间
1、左闭右闭
思路:
- while (left <= right) 要使用 <= ,因为left == right是有意义的,所以使用 <=;
- if (nums[middle] > target) right 要赋值为 middle - 1,因为当前这个nums[middle]一定不是target,那么接下来要查找的左区间结束下标位置就是 middle - 1
function search(nums, target) {
let mid, left = 0, right = nums.length - 1;
while (left <= right) {
mid = left + ((right - left) >> 1);
if (nums[mid] > target) {
right = mid - 1;
} else if (nums[mid] < target) {
left = mid + 1;
} else {
return mid;
}
}
return -1;
}
- 时间复杂度:O(log n);
- 空间复杂度:O(1);
2、左闭右开
思路:
- while (left < right),这里使用 < ,因为left == right在区间[left, right)是没有意义的;
- if (nums[middle] > target) right 更新为 middle,因为当前nums[middle]不等于target,去左区间继续寻找,而寻找区间是左闭右开区间,所以right更新为middle,即:下一个查询区间不会去比较nums[middle]
function search(nums, target) {
let mid, left = 0, right = nums.length;
while (left < right) {
mid = left + ((right - left) >> 1);
if (nums[mid] > target) {
right = mid;
} else if (nums[mid] < target) {
left = mid + 1;
} else {
return mid;
}
}
return -1;
}
- 时间复杂度:O(log n);
- 空间复杂度:O(1);
27. 移除元素
解题方法: 双指针法
特征: 通过一个快指针和慢指针在一个for循环下完成两个for循环的工作;2、定义快慢指针,快指针:寻找新数组的元素 ,新数组就是不含有目标元素的数组。慢指针:指向更新 新数组下标的位置;
重难点: 1、确定指针的移动策略;2、处理边界条件;
1、双指针法(快慢指针法)
思路:
- 定义快慢指针;
- 利用两个指针分别用于遍历和标记的不同行为高效解题(一个for循环下完成两个for循环的工作)
function removeElement(nums, target) {
let slow = 0;
for (let fast = 0; fast < nums.length; fast++) {
if (nums[fast] !== target) {
nums[slow++] = nums[fast];
}
}
return slow;
}
- 时间复杂度:O(n)
- 空间复杂度:O(1)