👉学习内容:数组理论基础;力扣第704题 二分查找;力扣第27题 移除元素
-
数组理论基础 文章链接
-
704. 二分查找 文章讲解 视频讲解
题目建议: 熟悉 根据 左闭右开,左闭右闭 两种区间规则 写出来的二分法。
拓展题型:
35.搜索插入位置
34.在排序数组中查找元素的第一个和最后一个位置
一、数组理论基础
- 内存地址(连续)、数组内容、下标(从0开始)
- 16进制中,8+4=c=12(可以少占一位便于表示)
- 数组元素不能删除,只能靠覆盖来操作
二、704. 二分查找
使用前提:数组为有序数组,同时题目还强调数组中无重复元素,因为一旦有重复元素,使用二分查找法返回的元素下标可能不是唯一的
💡关键问题在于对边界区间及开闭的处理:
①是>=还是> 【只有左闭右闭才是等于,如:[1,1]合法】
②是middle还是middle-1 【首先明确if里middle数值不等于target,所以替换时middle不能留在更新边界后的区间内。即:左闭右闭时都去掉;左闭右开时,右开本身不包含不用再去掉。】
- 复杂度分析
- 时间复杂度:O(logn),其中 n是数组的长度。
- 空间复杂度:O(1)。
- 两种区间思路
- 注:midlle与边界一方对比时都是纯大于小于号
①左闭右闭速记
- nums【-1】
- while 【<=】
- 右-1 ;左+1 (更新边界时)
int right = nums.size() - 1; // 定义target在左闭右闭的区间里,[left, right]
while (left <= right) { // 当left==right,区间[left, right]依然有效,所以用 <=
if (nums[middle] > target) {
right = middle - 1; // target 在左区间,所以[left, middle - 1]
} else if (nums[middle] < target) {
left = middle + 1; // target 在右区间,所以[middle + 1, right]
②左闭右开速记
- nums【=】
- while 【<】
- 右= ;左边同上
int right = nums.size(); // 定义target在左闭右开的区间里,即:[left, right)
while (left < right) { // 因为left == right的时候,在[left, right)是无效的空间,所以使用 <
if (nums[middle] > target) {
right = middle; // target 在左区间,在[left, middle)中
} else if (nums[middle] < target) {
left = middle + 1; // target 在右区间,在[middle + 1, right)中
- 704题解
class Solution {
public:
int search(vector<int>& nums, int target) {
//int nums[] ={-1,0,3,5,9,12};
int left = 0;
int right = nums.size() - 1;
while (left <= right) {
int middle =(left + right)/2;
//int middle = left + ((right - left) / 2);合并同类项后相同,写法不同,防止溢出
if (nums[middle] > target){
right = middle - 1;
}else if (nums[middle] < target){
left = middle +1;
}else {
return middle;
}
}
return -1;
}
};
💥易混淆:注意二分法原理、本题要求、left、right 以及 middle均为下标,只有nums[middle] 和target对应的为实际数值元素。
三、27. 移除元素
- 关于数组空间:
- 数组是连续的,所以“移除”元素事实上是(后面的元素往前移动)“覆盖”元素,所以本题中本身在末尾的"val"值是不处理的,仅仅在返回size变量值(对元素计数操作)时进行减法,但实际上在size个元素后面可能还会有其他元素。如数组[1,3,5,2,3,2] 移除val = 2后,返回的数组个数size=4,减少两个,但是最终的数组为[1,3,5,3,2] 。不要误以为最后的2被删除了,因为智能覆盖。
- 在做题时是否直接调用库函数,要综合考虑两点:
- 面试题目要看,该题目的是否为了考察原理,如果库只是其中的一小步,那就可以调用
- 实际应用时,要综合考虑调用具体库的复杂度(所以要对复杂度有概念)。例如:erase函数看似是一步解决,但是其复杂度不是“删除”O(1),而是“覆盖”O(n)。
- 暴力解法思路
- 一层for循环用来找元素
- 一层for循环用来覆盖前面的元素
- 双指针速记
- 快指针去找萝卜,慢指针挖坑。慢指针填完一个萝卜挖一个坑(slow ++),即慢指针填上一个元素就会指向下一个空位处,所以最后返回的slow值正好对应于前面的元素个数。slow为下标从0开始,最终的slow对应于最后一个没填的坑。
- 题解
class Solution {
public:
int removeElement(vector<int>& nums, int val) {
int slow = 0;
for(int fast =0; fast < nums.size(); fast++){
if (nums[fast] != val){
nums[slow] =nums[fast];
slow++;
}
}
return slow;
}
};