1 基础知识:数组基础
文档讲解:代码随想录
数组的在内存空间上是连续的,同时每一个元素的数据类型相同
数组的下标从0开始
数组的地址空间连续
由于地址空间的连续性,在删除和添加元素是,会对后边的所有元素造成影响,需要重新覆盖其后的所有元素
数组的元素只能覆盖,不能删除
二维数组的地址空间也是线性连续的
2 LeetCode704 二分查找
题目:给定一个 n
个元素有序的(升序)整型数组 nums
和一个目标值 target
,写一个函数搜索 nums
中的 target
,如果目标值存在返回下标,否则返回 -1
。
关键词:有序,不重复,查找
关于二分查找有两种边界条件。为什么要区分呢?因为每次更新边界变量时要遵循统一的边界条件。
左闭右开:[left, right)
class Solution {
public:
int search(vector<int>& nums, int target) {
int left = 0;
int right = nums.size(); // 开区间[left, right)
while(left < right){ // 开区间不能重合,用小于
int mid = left + (right - left) / 2; // 防止溢出
if(nums[mid] < target){ // mid已经确认了比较小,该往mid的右边建立[mid + 1, right)
left = mid + 1;
}else if(nums[mid] > target){ // mid已经确认比较大, 该往当前的mid的左边建立[letf, mid)
right = mid;
}else if(nums[mid] == target){ // mid正好,返回下标
return mid;
}
}
return -1;
}
};
注意:一开始的右边界 int right = nums.size() 是超出数组长度的!
左闭右闭:[left, right]
class Solution {
public:
int search(vector<int>& nums, int target) {
int left = 0;
int right = nums.size() -1; // 左闭右闭区间[left, right]
while(left <= right){ // 左闭右闭区间可以重合,用<=
int mid = left + (right - left) / 2; // 防止溢出
if(nums[mid] < target){ // mid已经确认了比较小,该往mid的右边建立[mid + 1, right]
left = mid + 1;
}else if(nums[mid] > target){ // mid已经确认比较大, 该往当前的mid的左边建立[letf, mid - 1]
right = mid - 1;
}else if(nums[mid] == target){ // mid正好,返回下标
return mid;
}
}
return -1;
}
};
总结:
二分查找针对有序的不重复的一组数据。
二分法思路:
以序列的边界作为区间
在区间成立的情况下
区间的中间点进行判断
如果满足条件则返回中间点下标
不满足条件则更新区间边界(统一的边界条件)
注意:
1 数组越界问题:右开区间直接用vector的.size();右闭区间采用.size() - 1。
2 两个数之和内存溢出问题:mid = left + (right - left) / 2。
3 LeetCode27.移除元素
题目:给你一个数组 nums
和一个值 val
,你需要 原地 移除所有数值等于 val
的元素,并返回移除后数组的新长度;不要使用额外的数组空间,你必须仅使用 O(1)
额外空间并 原地 修改输入数组;元素的顺序可以改变。你不需要考虑数组中超出新长度后面的元素。
暴力解法:
首先查找需要删去的元素,然后使用全覆盖实现该元素的删除
class Solution {
public:
int removeElement(vector<int>& nums, int val) {
int size = nums.size(); // 取出容器长度,右开边界
for(int i = 0; i < size; i++){
if(nums[i] == val){ // 查找目标元素
for(int j = i; j < size - 1; j++){ // 全覆盖实现“删除”功能
nums[j] = nums[j+1];
}
size--; // “删除”一个元素后,数组长度减一
i--; // “删除”一个元素后,新的元素一道当前位置,不需要向下走一步,自减以抵消最外层循环的自增
}
}
return size;
}
};
很有意思的暴力解法,其中的数组上限是动态变化的!
双指针法:
如何在暴力解法的规律之上再次分析?
快指针用来查找目标值
慢指针用来覆盖
class Solution {
public:
int removeElement(vector<int>& nums, int val) {
int fastIndex = 0; // 定义慢指针
int slowIndex = 0; // 定义快指针
int size = nums.size(); // 右开边界
int length = size; // 返回长度
for (fastIndex; fastIndex < size; fastIndex++){
if(nums[fastIndex] == val){ // 快指针索引到了目标值,需要让快慢指针异步
length--;
nums[slowIndex] = nums[fastIndex];
continue;
}
nums[slowIndex] = nums[fastIndex]; // 1 最简单的情形,覆盖一下
slowIndex++; // 更新慢指针
}
return length;
}
};
代码经处理后:
class Solution {
public:
int removeElement(vector<int>& nums, int val) {
int slowIndex = 0;
int size = nums.size(); // 右开边界
for (int fastIndex = 0; fastIndex < size; fastIndex++){
if(nums[fastIndex] == val){
continue; // 检索到目标之后的操作
}
// 基础操作
nums[slowIndex] = nums[fastIndex];
slowIndex++;
}
return slowIndex;
}
};
附上参考答案:
// 时间复杂度:O(n)
// 空间复杂度:O(1)
class Solution {
public:
int removeElement(vector<int>& nums, int val) {
int slowIndex = 0;
for (int fastIndex = 0; fastIndex < nums.size(); fastIndex++) {
if (val != nums[fastIndex]) {
nums[slowIndex++] = nums[fastIndex];
}
}
return slowIndex;
}
};
双指针可以用于无序序列中每次查找都有一次操作的情况!