代码随想录|704.二分查找、27.移除元素
数组
- 数组在内存中的表现是一段连续的空间,每一个地址都是相同的数据类型
- 增加或者"删除"(覆盖)操作,需要移动对应下标"之后"的所有元素
二分法
使用条件:有序数组,无重复元素
循环更改中位数,要注意判断区间在整个循环过程中应当保持一致。
比如[left,right],那么在更改的时候left = middle+1或者right = middle -1。右边界应当为size()-1。并且判断循环结束的条件为left>right
如果为[left,right)那么在更改的时候left = middle +1或者right = middle,右边界此时应当为size(),此时结束的条件为left>=right
leetcode704二分查找
文档讲解:代码随想录
视频讲解:手把手带你撕出正确的二分法 | 二分查找法 | 二分搜索法 | LeetCode:704. 二分查找
状态:方法错误,不能全过
-
自己的思路
用middle和数组长度作为判断和循环终止条件。会出现很多问题,首先是对于头和尾的元素无法考虑进去,且middle是通过数组长度/2得到的整数,可能会丢弃掉某些值 -
二分查找思路
注意到题目中,升序(有序)并且无重复的两个条件,所以可以考虑二分查找方法。
在使用二分查找方法的过程中,需要注意循环终止条件和中值的变换要保持一致以及相互对应。
下方为使用[left,right]的方法解决,得到时间复杂度为O(logN)。
class Solution {
public:
int search(vector<int>& nums, int target) {
int length = nums.size()-1;
int left = 0;
int right = length;
//如果是[left,right),right = length+1 ,left<right , right = middle;
while(left <= right)
{
int middle = (right+left)/2;
//此时中位数小,说明target在右边,更新left,计算middle
if(nums[middle] < target)
{
left = middle+1;
}
else if(nums[middle] > target)
{
right = middle-1;
}
else
{
return middle;
}
}
return -1;
}
};
leetcode35搜索插入位置
文档讲解:代码随想录
状态:对于头和尾的插入
- 思路
看到有序和无重复,相当二分查找。本题遇上题不一样的是需要将-1变为要插入的位置。
本题使用的左闭右开的方法,所以我们的判断区间是[left,right)。right即是我们的middle值,也是我们需要插入的位置。
如果我们要插入的是头部,那么此时的区间为[0,0),如果我们要插入的是尾部那么此时的区间也是[left,right==nums.size()),所以最后返回的下标也是right。
得到时间复杂度O(logN)
class Solution {
public:
int searchInsert(vector<int>& nums, int target) {
int length = nums.size();
int left = 0;
int right = length;
while(left < right)
{
int middle = (left+right)/2;
if(nums[middle] < target)
{
left = middle+1;
}
else if(nums[middle] > target)
{
right = middle;
}
else
{
return middle;
}
}
//如果没有则进行插入操作
//插入后,后面元素需要向后移动
//我们需要判断如果target插入到头和尾的时候
//如果是头[0,0)
//如果是尾[left,right)
return right;
}
};
在排序数组中查找元素的第一个和最后一个位置
文档讲解:代码随想录
状态:边界状态的判断
- 本题最大区别就是存在重复数据了,返回的是一个数组,可以将else中的return变为将开始位置和结束位置添加到数组里面
- 我们使用了分别判断左边界和右边界的方法,
获取左边界,就相当于一直获取右范围,直到右范围对应的值小于了target,如果没有获得左边界的值,那么就说明target超过了这个数组的右端。
获取右边界,就相当于一直获取左范围,直到左范围对应的值大于了target,如果没有获得右边界的值,那么就说明target小于这个数组的左端。
class Solution {
public:
//获取左边界,同时判断target大于右边界的情况
int GetLeftInd(vector<int>& nums, int target){
int length = nums.size()-1;
int left = 0;
int right = length;
int leftInd = -2;
while(left <= right)
{
int middle = (left+right)/2;
if(nums[middle] >= target)
{
//leftInd的值会比正确的index小1
right = middle - 1;
leftInd = right;
}
else
{
left = middle+1;
}
}
return leftInd;
}
//获取右边界,同时处理target小于左边界的情况
int GetRightInd(vector<int>& nums, int target){
int length = nums.size()-1;
int left = 0;
int right = length;
int RightInd;
while(left <= right)
{
int middle = (left+right)/2;
if(nums[middle] <= target)
{
//rightInd的值会比正确的index大1
left = middle + 1;
RightInd = left;
}
else
{
right = middle-1;
}
}
return RightInd;
}
public:
vector<int> searchRange(vector<int>& nums, int target) {
//定义一个返回数组
int left = GetLeftInd(nums,target);
int right = GetRightInd(nums,target);
//当target小于最左值,或者大于最右值时候
if(left == -2 || right == -2)
{
return {-1,-1};
}
else if(right-left>1)
{
return {left+1,right-1};
}
else
{
return {-1,-1};
}
}
};
X的平方根
- 二分法
相当于从0到x的整数范围内寻找最大的那个整数a使得a*a<=x。注意到该数组序列是一个升序的,然后无重复的序列。不断的去寻找中间值a。
class Solution {
public:
int mySqrt(int x) {
int left = 0;
int right = x;
int res = -1;
while(left <= right)
{
int middle = (left+right)/2;
//long long为leetcode给的数据
if((long long)middle * middle > x)
{
right = middle -1;
}
//当middle*middle<=x时,才可能得到正确的整数平方根
//但还是不够,因为可能得到较小值,直到left和right相遇。
else
{
res = middle;
left = middle + 1;
}
}
return res;
}
};
由于题意是middlemiddle <= x的middle所以应当在使用左值返回,如果找middlemiddle>=x的数,那么应当使用right+1返回
有效的完全平方数
- 与上题一样,同样使用二分法,只不过需要更改判断条件,只有a*a == x的时候返回true,其余情况返回false。
class Solution {
public:
bool isPerfectSquare(int num) {
int left = 0;
int right = num;
while(left <= right)
{
int middle = (left+right)/2;
if((long long) middle*middle > num)
{
right = middle-1;
}
else if((long long) middle*middle < num)
{
left = middle+1;
}
else
{
return true;
}
}
return false;
}
};
移除元素
文档讲解:代码随想录
视频讲解:《代码随想录》算法视频公开课 (opens new window):数组中移除元素并不容易!LeetCode:27. 移除元素
状态:暴力解法
- 暴力解法
双重循环,第一重循环用于寻找判断当前下标是否与要删除的值相等,第二重循环用于将当前下标的之后的数向前移动,更新数组。
要注意在第二重循环中,要注意当更新数组后,同时要更新下标值和循环终止的长度。
class Solution {
public:
int removeElement(vector<int>& nums, int val) {
int length = nums.size();
for(int i=0;i<length;i++)
{
if(nums[i] == val)
{
for(int j = i;j<length-1;j++)
{
nums[j] = nums[j+1];
}
i--;//i之后的值下标都减一了
length--;//数组长度也要减少
}
}
return length;
}
};
- 双指针
通过一个快指针和慢指针在一个for循环下完成两个for循环的工作
快指针:寻找新数组的元素,新数组就是不含有目标元素的数组
慢指针:指向更新,新数组下标的位置
快指针带动慢指针移动
在本题中,
快指针用于判断当前指向的数是否是要删除的数,如果是,移动快指针,保持慢指针。
慢指针用来存储新数组的下标,就是当快指针指向的值不是要删除的值,那么将慢指针指向的值更新为快指针指向的值。
如果是删除的值,那么就不做处理,只是移动快指针。
class Solution {
public:
int removeElement(vector<int>& nums, int val) {
int left = 0;
int right = 0;
while(right < nums.size())
{
if(nums[right] != val)
{
nums[left] = nums[right];
left++;
}
right++;
}
return left;
}
};
双指针的解题一般为
//循环体
//判断,本文是判断不等的所以if不等,如果保留相等的,那么就是if相等
//判断的条件需要和题目相同即是同一个真命题,如果是否命题那又要用一个循环来判断并且更复杂
//慢指针移动+题目要求操作,只在满足题目需求是才能移动
//快指针移动,不论什么情况都要移动
//