704. 二分查找
题目链接: 力扣
给定一个 n 个元素有序的(升序)整型数组 nums 和一个目标值 target ,写一个函数搜索 nums 中的 target,如果目标值存在返回下标,否则返回 -1。
示例1
输入: nums = [-1,0,3,5,9,12], target = 9
输出: 4
解释: 9 出现在 nums 中并且下标为 4
示例2
输入: nums = [-1,0,3,5,9,12], target = 2
输出: -1
解释: 2 不存在 nums 中因此返回 -1
基本概念
-
时间复杂度:时间复杂度的计算并不是计算程序具体运行的时间,而是算法执行语句的次数。
-
空间复杂度:空间复杂度是对一个算法在运行过程中临时占用存储空间大小的量度。
-
使用二分法的前提:
- 数组为有序数组
- 数组中无重复元素
思考过程
- 数据结构与算法课程上也学习了二分查找,分为forgetful和recognize,forgetful和recognize二分查找
- 视频思路:根据区间开闭,写出while循环条件和left,right更新的数值
方法一:左闭右闭写法
class Solution {
public:
int search(vector<int>& nums, int target) {
//左闭右闭,[left,right]
int left = 0;
int right = nums.size() - 1; //因为右区间取得到数值,right的数值必须是合法的
while(left <= right){ //[left, right],左右可以相等
int middle = left + (right - left) / 2; //防止两个正数相加会溢出
if(nums[middle] < target) //middle数值比target数值小,因为区间是闭的,一定不在区间里
left = middle + 1; //[middle + 1, right],把一定不在区间内的情况去掉
else if(nums[middle] > target)
right = middle -1; //[left, middle - 1]
else
return middle; //存在的下标
}
return -1; //不存在
}
};
- 时间复杂度:
- 空间复杂度:
方法一:左闭右开写法
class Solution {
public:
int search(vector<int>& nums, int target) {
//左闭右开,[left,right)
int left = 0;
int right = nums.size(); //因为右区间取不到数值,要小于right但是要取遍数组的值,所以right比数组最大元素下标大1
while(left < right){ //[left, right),左右不可以相等
int middle = left + (right - left) / 2; //防止两个正数相加会溢出,等同于(left + right) / 2
if(nums[middle] < target) //落入右区间,左边为闭,不能把middle取到
left = middle + 1; //[middle + 1, right),把一定不在区间内的情况去掉
else if(nums[middle] > target) //落入左区间,右边为开,取middle才能包括剩下的数值
right = middle; //[left, middle)
else
return middle; //存在的下标
}
return -1; //不存在
}
};
反思与回顾
- 更新左右区间的值应该用left和right,不能用middle
- middle一开始写到循环外部了,middle的数值应该是每次循环都需要更新的,应该放在循环内
- 对于区间边界值的取用就是要找到能把下一次查找的元素全都囊括起来的数值
- 二分法的两个迭代法的时间复杂度和空间复杂度:
时间复杂度:时间复杂度的计算并不是计算程序具体运行的时间,而是算法执行语句的次数。
假设总共有n个元素,每次查找的区间大小就是n,n/2,n/4,…,n/2^k,其中k就是循环的次数,
n/2^k >= 1(1最坏的情况,即还剩一个元素),
令n/2^k=1,
可得k=log2n(以2为底,n的对数),
所以时间复杂度可以表示O(log2n)。
空间复杂度:空间复杂度是对一个算法在运行过程中临时占用存储空间大小的量度。
因为变量只创建一次,所以空间复杂度为O(1)。
27. 移除元素
题目链接: 力扣
示例1
输入:nums = [3,2,2,3], val = 3
输出:2, nums = [2,2]
解释:函数应该返回新的长度 2, 并且 nums 中的前两个元素均为 2。你不需要考虑数组中超出新长度后面的元素。例如,函数返回的新长度为 2 ,而 nums = [2,2,3,3] 或 nums = [2,2,0,0],也会被视作正确答案。
示例2
输入:nums = [0,1,2,2,3,0,4,2], val = 2
输出:5, nums = [0,1,4,0,3]
解释:函数应该返回新的长度 5, 并且 nums 中的前五个元素为 0, 1, 3, 0, 4。注意这五个元素可为任意顺序。你不需要考虑数组中超出新长度后面的元素。
说明:
为什么返回数值是整数,但输出的答案是数组呢?
请注意,输入数组是以「引用」方式传递的,这意味着在函数里修改输入数组对于调用者是可见的。
你可以想象内部操作如下
// nums 是以“引用”方式传递的。也就是说,不对实参作任何拷贝
int len = removeElement(nums, val);
// 在函数里修改输入数组对于调用者是可见的。
// 根据你的函数返回的长度, 它会打印出数组中 该长度范围内 的所有元素。
for (int i = 0; i < len; i++) {
print(nums[i]);
}
方法一:暴力法
思考过程
- 刚开始想到的就是数组整体前移,类似于插入排序的过程
- 视频思路:双指针,用一个fast指针遍历数组,一个slow指针更新数组,也不用开一个新数组
class Solution {
public:
int removeElement(vector<int>& nums, int val) {
int i,j;
int len = nums.size();
for(i = 0;i < nums.size();i++){
if(nums[i] == val){
j = i + 1;
while(j < len){
nums[j - 1] = nums[j]; //数组后面整体左移
j++;
}
// i以后的数值都向前移动了一位,所以i也向前移动一位
i--;
// 数组前移后,数组大小减一
len--;
}
}
return len;
}
};
- 时间复杂度:双层for循环,所以为O(n^2)
- 空间复杂度:O(1)
方法二:双指针法
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];
}
return slow;
}
};
- 时间复杂度:O(n)
- 空间复杂度:O(1)
反思与回顾
- 暴力求解法中要定义一个len长度,不能直接使用size,因为只是更新数组的数值,大小是不变的
总结
第一次认真刷力扣,还不是很熟悉,看了题目思考了一会儿就直接看视频了,花了一个下午磨磨蹭蹭。起码看懂了还自己敲出来了,抵制CV!!!