目录
704. 二分查找
题目: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
二、解题思路
比较基础简单的题目,利用二分算法即可。
首先设置left和right两个整型变量来记录数组的首尾。由于给出的是vector数组,所以数组开头位置下标为0,数组结尾是内置函数size得出数组的大小并-1(数组下标从零开始计算,所以得出的size大小要-1才是指向最后一个数组)。此时的指向是这样的:
找到数组中间的位置,也就是mid=(left+right)/2,如果此时mid指向的数字等于我们要查找的数字,返回mid即可。
如果mid指向的数字大于我们要查找的数字,由于数组是升序排序,说明我们要找的数字在从左边起到mid-1这个范围中,这时候令right=mid,继续执行二分查找。
如果mid指向的数字小于于我们要查找的数字,由于数组是升序排序,说明我们要找的数字在从mid+1起到右边指向数字这个范围中,这时候令left=mid,继续执行二分查找。
如果此时mid指向的数字不等于我们要查找的数字,left和right有一方更新了所指向的数组的位置,也就是查找范围减半,此时再在新的查找范围里面继续二分查找,直到找到所要数字返回为止。如果更新到righ<left,就说明遍历了数组后还是没有找到目标数字,此时跳出循环,返回-1。
三、解题代码
//非递归方法
class Solution {
public:
int search(vector<int>& nums, int target) {
int right = nums.size()-1;//数组结尾位置(初始)
int left = 0; //数组开头位置(初始)
while(left<=right){//如果数组一开始就为空的话会有left>right
int mid = (left+right)/2;//二分位置
if(nums[mid]>target){
right = mid-1;//中间位置比目标数大,更新右边
}
else if(nums[mid]<target){
left = mid+1;//中间位置比目标数大,更新左边
}
else if(nums[mid]==target){
return mid;
}
}
return -1;
}
};
四、提交结果
五、解题感想
二分查找算法算很基础,也很容易理解的算法,做起来不难。不过一开始确实没注意到左闭右开、左闭右闭、左开右闭的说法。一般用的是左闭右开,可能是担心内存泄漏等问题?总之二分法就是这么个思想啦。
27.移除元素
题目:27.移除元素
文档:代码随想录
一、题目描述
给你一个数组 nums
和一个值 val
,你需要原地移除所有数值等于 val
的元素,并返回移除后数组的新长度。
不要使用额外的数组空间,你必须仅使用 O(1)
额外空间并原地修改输入数组。
元素的顺序可以改变。你不需要考虑数组中超出新长度后面的元素。
说明:
为什么返回数值是整数,但输出的答案是数组呢?请注意,输入数组是以「引用」方式传递的,这意味着在函数里修改输入数组对于调用者是可见的。
示例 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
注意这五个元素可为任意顺序。你不需要考虑数组中超出新长度后面的元素。
二、解题思路
很久没接触双指针的题了,一开始看到是vector容器第一反应是用erase,确实也能用erase,或者暴力双循环解法,但其实没必要,因为输出结果的元素可以为任意顺序,而且不需要考虑超出新长度后面的数组,所以可以用双指针法,时间复杂度更小(算法,能省则省啦……)。
这道题目我采用的是优化版的双指针,优化版的双指针在最坏情况下只需要遍历一遍数组,而普通双指针在最坏情况下要遍历两遍数组。
首先设置左指针left = 0指向数组头,设置右指针right = nums.size()准备指向数组尾。
不设置right = nums.size()-1是因为接下来的循环判断条件是left != right,如果数组长度为0就会陷入死循环。如果循环判断条件改成left <= right的话可将right初始化为right = nums.size()-1,时间和空间消耗也差不多。
以left != right为判断条件,接下来进入循环:
如果left指向的数字等于val,则需要删除,因为这里的要求是输出结果的元素可以为任意顺序,而且不需要考虑超出新长度后面的数组,所以这时候我们用右指针right-1指向的数字覆盖掉left指向的数字,执行操作nums[left]=nums[right-1]。这时候left不需要移动,因为我们不知道nums[right-1]是否等于val,需要等待下一次循环进行判断,而由于nums[right-1]已经使用过去覆盖数组了,我们将right往前移一格指向还没指向的数字,也就是执行操作right--。
如果left指向的数字不等于val,这个数就不需要进行处理,跳过就行,left往后移一格,但此时right不能移动,因为还没有使用它所指向的数字。
当left和right重合的时候,也就把数组内所有的数字判断了一遍,将所有有val的值都用非val的值覆盖了,此时left的大小就是数组长度大小。
至于后面right使用并跳过的数字,都用于覆盖left前有val值的数,所以left指向的数及其之前的数就是删掉val后还剩下的数,至于结束时候left指向的数之后的数是不会进行输出的,无需处理。
记录一下双指针的解法。未优化版的双指针是快慢指针之分。设fast和slow分别为快指针和慢指针,初始化的时候都为0(指向数组开头)。快指针是无论有没有遇到val都继续向后探索的,而慢指针是遇到val后停下来,等快指针指到其之后最近的一个非val值后,将其覆盖,然后慢指针向后移一格。快指针遍历结束后,慢指针大小就是新数组的长度。
三、解题代码
用优化双指针的代码
class Solution {
public:
int removeElement(vector<int>& nums, int val) {
int left = 0, right = nums.size();
while(left != right){
if(nums[left] == val){
nums[left] = nums[right-1];
right--;
}
else{
left++;
}
}
/*如果循环判断条件为left<=right的话可以写成
* int left = 0, right = nums.size()-1;
* while(left <= right){
* if(nums[left] == val){
* nums[left] = nums[right];
* right--;
* }
* else{
* left++;
* }
* }
*/
return left;
}
};
用双指针的代码
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;
}
};
用vector内置函数erase(pos)的代码
class Solution {
public:
int removeElement(vector<int>& nums, int val) {
for(int i = 0; i < nums.size(); i++){
if(nums[i]==val){
nums.erase(nums.begin()+i);
i--;//删掉了其后所有元素都往前移动了一格,记得把i-1.
}
}
return nums.size();
}
};
四、提交结果
用优化双指针的提交结果
用双指针的提交结果
用vector内置函数erase(pos)的提交结果
五、解题感想
复习了一下双指针的用法。这道题能用双指针还是得因为输出结果的元素可以为任意顺序,而且不需要考虑超出新长度后面的数组。今天的两道题总体还是挺简单的。