今天主要刷了Leetcode二分查找相关的题目704、35、34、69、367,还有双指针的27。
数组:存放在连续内存空间上的相同类型数据的集合。
二分查找的局限性
适用于数组有序的情况,当数组中的数组无序的时候,二分查找失效。
二分查找复杂度
时间复杂度O(logn),每次查找区间折半。
空间复杂度O(1)
注意点
左闭右闭、左闭右开、右开左闭区间定义下,判断条件的改变。
C++
左闭右闭
class Solution {
public:
int search(vector& nums, int target) {
int left = 0, right = nums.size() - 1;//注意right在不同开闭区间-1和不减有区别
int middle;
while(left <= right){
middle = (left + right ) / 2;
if(nums[middle] > target){
right = middle - 1;//开闭区间的判别
}
else if(nums[middle] < target){
left = middle + 1;//开闭区间的判别
}
else{
return middle;
}
}
return -1;//用于判断如果没有数字在上面的情况
}
};
左闭右开
class Solution {
public:
int search(vector& nums, int target) {
int left = 0, right = nums.size();
int middle;
while(left < right){
middle = (left + right ) / 2;
if(nums[middle] > target){
right = middle;
}
else if(nums[middle] < target){
left = middle + 1;
}
else{
return middle;
}
}
return -1;
}
};
代码优化
middle = (left + right) / 2;
//可以写成下述,来防止数组溢出
middle = left + ((right - left) / 2)
这道题在上述题目的基础上进行,要求O(logn)复杂度,因此需要采用双指针的手段。虽然说题目要求插入,但是不需要真的插入操作,只需要返回要插入位置的下标就可以了。因此只需要在上题的基础上,将不属于数组的情况输出为-1改成需要插入的地址就可以了。
class Solution {
public:
int searchInsert(vector& nums, int target) {
int left = 0, right = nums.size() - 1;//注意right在不同开闭区间-1和不减有区别
int middle;
while(left <= right){
middle = left + (right - left) / 2;
if(nums[middle] > target){
right = middle - 1;//开闭区间的判别
}
else if(nums[middle] < target){
left = middle + 1;//开闭区间的判别
}
else{
return middle;
}
}
return left;//虽然说题目要求插入,但是不需要真的插入操作,只需要返回要插入位置的下标就可以了
}
};
代码随想录
中等难度,没法写出来。解题思路:先寻找左编解,然后寻找右边界。
class Solution {
public:
vector searchRange(vector& nums, int target) {
int leftBorder = getLeftBorder(nums, target);
int rightBorder = getRightBorder(nums, target);
// 情况一
if (leftBorder == -2 || rightBorder == -2) return {-1, -1};
// 情况三
if (rightBorder - leftBorder > 1) return {leftBorder + 1, rightBorder - 1};
// 情况二
return {-1, -1};
}
private:
int getRightBorder(vector& nums, int target) {
int left = 0;
int right = nums.size() - 1;
int rightBorder = -2; // 记录一下rightBorder没有被赋值的情况
while (left <= right) {
int middle = left + ((right - left) / 2);
if (nums[middle] > target) {
right = middle - 1;
} else { // 寻找右边界,nums[middle] == target的时候更新left
left = middle + 1;
rightBorder = left;
}
}
return rightBorder;
}
int getLeftBorder(vector& nums, int target) {
int left = 0;
int right = nums.size() - 1;
int leftBorder = -2; // 记录一下leftBorder没有被赋值的情况
while (left <= right) {
int middle = left + ((right - left) / 2);
if (nums[middle] >= target) { // 寻找左边界,nums[middle] == target的时候更新right
right = middle - 1;
leftBorder = right;
} else {
left = middle + 1;
}
}
return leftBorder;
}
};
力扣(LeetCode)官网 - 全球极客挚爱的技术成长平台
当然这道题有时间和空间复杂度更好的解法,但是在这里确实可以尝试二分法解决问题。
时间复杂度为O(logn),空间复杂度为O(1)。只需要比较中间值的平方与目标值的大小关系,就可以通过比较的结果调整上下界。
class Solution {
public:
int mySqrt(int x) {
int left = 0, right = x, ans = -1;
while(left <= right){
int middle = left + (right - left) / 2;
if((long long)middle * middle <= x){
ans = middle;
left = middle + 1;
}
else{
right = middle - 1;
}
}
return ans;
}
};
和上题类似,但是比上一题简单很多,主要在于最后输出的处理上,找到中间的数字,就输出true。
class Solution {
public:
bool isPerfectSquare(int num) {
int left = 0, right = num;
while(left <= right){
int middle = left + (right - left) / 2;
if((long long)middle * middle < num){
left = middle + 1;
}
else if((long long)middle * middle > num){
right = middle - 1;
}
else{
return true;
}
}
return false;
}
};
因为数组在存储空间上是连续的,因此删除元素的操作在实际实现上是通过元素覆盖的方式实现。
在C++中有erase函数,其时间复杂度为O(n)
C++中string类型的erase()函数详解_c++ string erase-CSDN博客
该题的主要实现erase函数的操作。
复杂度O(n^2)
使用两个for循环,来暴力求解。
class Solution {
public:
int removeElement(vector& nums, int val) {
int size = nums.size();
for (int i = 0; i < size; i++) {
if (nums[i] == val) { // 发现需要移除的元素,就将数组集体向前移动一位
for (int j = i + 1; j < size; j++) {//用j = i样例就不过了,循环太多
nums[j - 1] = nums[j];
}
i--; // 因为下标i以后的数值都向前移动了一位,所以i也向前移动一位
size--; // 此时数组的大小-1
}
}
return size;
}
};
复杂度O(n)
使用快慢指针,fast指针用于搜索比较,slow指针用于赋值覆盖。从数组第一位开始,fast指针开始比较,如果fast指针指向的数值不等于我们要删除的数值,那么我们就可以将该值赋给slow指针。直到最后循环结束,返回slow指针。
如果是前后指针,在最后输出slow的时候,要记得反向输出。
class Solution {
public:
int removeElement(vector& nums, int val) {
int fast = 0, slow = 0;
while(fast < nums.size()){
if(nums[fast] != val){
nums[slow] = nums[fast];
slow++;
}
fast++;
}
return slow;
}
};
注意题干最后的输出,是向量,但是输出一个数字就会返回对应长度的向量。