LeetCode-704.二分查找
视频讲解:手把手带你撕出正确的二分法 | 二分查找法 | 二分搜索法 | LeetCode:704. 二分查找_哔哩哔哩_bilibili
数组基础
数组:存放在连续内存空间上的相同类型数据的集合。
- 数组下标都是从0开始的
- 数组内存空间的地址是连续的
- c++中二维数组在地址空间中是连续的
- 删除或添加数组时需要移动大量元素
LeetCode-704.二分查找
思考:本题中提到数组为有序数组和数组无重复元素——>符合使用二分法的前提条件。
二分法的诀窍——>循环不变量:对于区间的定义是不变量,在二分查找的过程中,保持不变量,就是在while寻找中每一次边界的处理都要坚持根据区间的定义来操作,这就是循环不变量规则。 二分法中,区间的定义一般分为两种,左闭右闭区间和左闭右开区间,由此本题产生了两种二分解法。
注意:middle的初值计算可以使用位运算代替除运算,提高效率;注意middle的取值变化(与区间的取法紧密相关)。
左闭右闭
//两种方法都是二分法寻值
//法一:左闭右闭区间
int search1(vector<int>& nums, int target) {
int left = 0;
int right = nums.size() - 1;
while (right >= left) {
int middle = left +( (right - left) >> 1);
if (nums[middle] < target) {
// 目标在middle右边
left = middle + 1;
}
else if (nums[middle] > target) {
// 目标在middle左边
right = middle - 1;
}
else {
// middle == target 即找到目标
return middle;
}
}
// 未找到目标
return -1;
}
时间复杂度:O(log n)、空间复杂度:O(1)。
左闭右开
int search2(vector<int>& nums, int target) {
int left{0};
//区间右开 所以right为区间末尾+1
int right{int(nums.size()) };
while (right > left) {
int middle{left +( (right - left) >> 1)};
if (nums[middle] < target) {
left = middle + 1;
}
else if (nums[middle] > target) {
right = middle;
}
else {
return middle;
}
}
return -1;
}
时间复杂度:O(log n)、空间复杂度:O(1)。
LeetCode-27.移除元素
思路:数组的元素在内存地址中是连续的,不能单独删除数组中的某个元素,只能覆盖。题目中提炼出信息点:不需要考虑数组中超出新长度后面的元素、不能使用额外的数组空间,只能用O(1)算法原地修改数组、数组顺序可以改变。
暴力解法
两层for循环,一层遍历数组元素,一层循环更新数组(后面元素向前覆盖)
// 暴力解法
int removeElement1(vector<int>& nums, int val) {
int count = nums.size();
for (int i = 0; i < count; ++i) {
if (nums[i] == val) {
for (int j = i + 1; j < count; ++j) {
nums[j-1] = nums[j];
}
//删掉了一个元素,i应该往前移一位
--i;
--count;
}
}
return count;
}
快慢指针法*
双指针法(快慢指针法): 通过一个快指针和慢指针在一个for循环下完成两个for循环的工作。
定义快慢指针
- 快指针:寻找新数组的元素 ,新数组就是不含有目标元素的数组
- 慢指针:指向新数组中等待更新的下标位置
- 个人理解:快慢指针从低地址同时向高地址同步出发,遇见目标值则慢指针停留一步,快指针领先一步,将后面的元素向前移位覆盖,再次遇见目标值则重复以上步骤。
int removeElement2(vector<int>& nums, int val) {
int slowPointer = 0;
for (int fastPointer = 0; fastPointer < nums.size(); ++fastPointer) {
if (val != nums[fastPointer]) {
nums[slowPointer++] = nums[fastPointer];
}
}
return slowPointer;
}
时间复杂度:O(n),空间复杂度O(1).
相向双指针法
定义左右指针,左指针从低地址出发定位目标值,右指针从高地址出发定位非目标值,用右值覆盖左值,循环结束的条件为left <= right。具体见注释。
// 相向双指针 打乱了原顺序
int removeElement3(vector<int>& nums, int val) {
int leftPointer = 0;
int rightPointer = nums.size() - 1;
// 循环条件
while (leftPointer <= rightPointer) {
// 左指针找到为val的元素位置
while (leftPointer <= rightPointer && nums[leftPointer] != val ) {
++leftPointer;
}
// 右指针找到不为val的元素位置
while (leftPointer <= rightPointer && nums[rightPointer] == val) {
--rightPointer;
}
// 用右指针的元素覆盖左指针值为val的元素
// leftPointer == rightPointer则无动作
if (leftPointer < rightPointer) {
//覆盖后 指针移动一位
nums[leftPointer++] = nums[rightPointer--];
}
}
// 左指针一定为末尾元素下标加一,即是数组大小
return leftPointer;
}
时间复杂度:O(n),空间复杂度:O(1)。
第一天打卡,时间安排不太妥当,明天补上额外练习题,cpper-go!