一、二分查找
注意:怕自己背题,所以这里简单写写记录一下:
- left和right闭包不同,判定条件不同,mid取值不同等
- (left + right) / 2可能导致int溢出问题
借助二叉树分析,这种方法时间复杂度应该为O(log2n)
题目一:704. 二分查找
给定一个 n
个元素有序的(升序)整型数组 nums
和一个目标值 target
,写一个函数搜索 nums
中的 target
,如果目标值存在返回下标,否则返回 -1
。
/*
* @lc app=leetcode.cn id=704 lang=cpp
*
* [704] 二分查找
*/
// @lc code=start
class Solution {
public:
int search(vector<int>& nums, int target) {
int left = 0;
int right = nums.size()-1;
while (left <= right) {
int mid = left + (right - left) / 2;
if (nums[mid] < target)
left = mid + 1;
else if(nums[mid] > target)
right = mid - 1;
else
return mid;
}
return -1;
}
};
// @lc code=end
其他类似题
题目二:35. 搜索插入位置
给定一个排序数组和一个目标值,在数组中找到目标值,并返回其索引。如果目标值不存在于数组中,返回它将会被按顺序插入的位置。请必须使用时间复杂度为 O(log n)
的算法。
/*
* @lc app=leetcode.cn id=35 lang=cpp
*
* [35] 搜索插入位置
*/
// @lc code=start
class Solution {
public:
int searchInsert(vector<int>& nums, int target) {
int left = 0;
int right = nums.size()-1;
while (left <= right) {
int mid = left + (right - left) / 2;
if (nums[mid] < target)
left = mid + 1;
else if(nums[mid] > target)
right = mid - 1;
else
return mid;
}
return left;
}
};
// @lc code=end
题目三:34. 在排序数组中查找元素的第一个和最后一个位置
给你一个按照非递减顺序排列的整数数组 nums
,和一个目标值 target
。请你找出给定目标值在数组中的开始位置和结束位置。
如果数组中不存在目标值 target
,返回 [-1, -1]
。
你必须设计并实现时间复杂度为 O(log n)
的算法解决此问题。
第一次编写:
/*
* @lc app=leetcode.cn id=34 lang=cpp
*
* [34] 在排序数组中查找元素的第一个和最后一个位置
*/
// @lc code=start
class Solution {
public:
vector<int> searchRange(vector<int>& nums, int target) {
int flag = 1;
int begin = -1;
int end = -1;
int left = 0;
int right = nums.size() - 1;
while (left <= right) {
int mid = left + (right - left) / 2;
if (nums[mid] < target)
left = mid + 1;
else if(nums[mid] > target)
right = mid - 1;
else {
if (flag)
begin = mid;
end = mid;
flag = 0;
}
}
return {begin, end};
}
};
// @lc code=end
出错了,两个思路错误:
nums[mid] == target这里是需要移动,不然会一直死循环
例如:不同数组[7,7,7],[8,7,7],[7,7,8],target=7,第一个找到的位置也不能界定边界
通过判断条件nums[mid] == target时,是移动left,还是right,来确定左右边界。
/*
* @lc app=leetcode.cn id=34 lang=cpp
*
* [34] 在排序数组中查找元素的第一个和最后一个位置
*/
// @lc code=start
class Solution {
public:
int binarySearch(vector<int>& nums, int target, bool flag) {
int left = 0;
int begin = -1;
int end = -1;
int right = nums.size() - 1;
while (left <= right) {
int mid = left + (right - left) / 2;
// flag为true时,寻找右边界
if ((nums[mid] < target) || (flag && nums[mid] <= target)) {
left = mid + 1;
end = mid;
}
else {
right = mid - 1;
begin = mid;
}
}
return flag? end: begin;
}
vector<int> searchRange(vector<int>& nums, int target) {
int rightIdx = binarySearch(nums, target, true);
int leftIdx = binarySearch(nums, target, false);
if (leftIdx != -1 && rightIdx != -1 && nums[leftIdx] == target && nums[rightIdx] == target)
return {leftIdx, rightIdx};
return {-1, -1};
}
};
// @lc code=end
因为两次寻找边界,只是nums[mid] == target判定不一样,所以这里建立一个flag判断一下是左边界还是右边界。看了一下官方答案的思路,我容易逻辑混乱,所以按照我自己的思路写了~
题目四:69. x的平方根
给你一个非负整数 x
,计算并返回 x
的 算术平方根 。
由于返回类型是整数,结果只保留 整数部分 ,小数部分将被 舍去 。
注意:不允许使用任何内置指数函数和算符,例如 pow(x, 0.5)
或者 x ** 0.5
。
/*
* @lc app=leetcode.cn id=69 lang=cpp
*
* [69] x 的平方根
*/
// @lc code=start
class Solution {
public:
int mySqrt(int x) {
int left = 0;
int right = x;
while (left <= right) {
int mid = left + (right - left) / 2;
int num = mid * mid;
if (num < x) {
left = mid + 1;
} else if (num > x){
right = mid - 1;
} else {
return mid;
}
}
return left - 1;
}
};
// @lc code=end
报错了,mid溢出了
/*
* @lc app=leetcode.cn id=69 lang=cpp
*
* [69] x 的平方根
*/
// @lc code=start
class Solution {
public:
int mySqrt(int x) {
int left = 0;
int right = x;
while (left <= right) {
long long mid = left + (right - left) / 2;
long long num = mid * mid;
if (num < x) {
left = mid + 1;
} else if (num > x){
right = mid - 1;
} else {
return mid;
}
}
return left - 1;
}
};
// @lc code=end
题目五:367. 有效的完全平方数
给你一个正整数 num
。如果 num
是一个完全平方数,则返回 true
,否则返回 false
。
完全平方数 是一个可以写成某个整数的平方的整数。换句话说,它可以写成某个整数和自身的乘积。不能使用任何内置的库函数,如 sqrt
。
/*
* @lc app=leetcode.cn id=367 lang=cpp
*
* [367] 有效的完全平方数
*/
// @lc code=start
class Solution {
public:
bool isPerfectSquare(int num) {
int left = 0;
int right = num;
while (left <= right) {
long long mid = left + (right - left) / 2;
long long mid_pow = mid * mid;
if (mid_pow < num) {
left = mid + 1;
} else if (mid_pow > num){
right = mid - 1;
} else {
return true;
}
}
return false;
}
};
// @lc code=end
总结:
- 二分查找的两个作用:
- 搜索目标值
- 搜索目标值左边界/右边界
- 实现取决于”序列中位数==目标值“的处理
二、移除元素
双指针方法:一种是不改变相对位置,另一种是改变相对位置
题目一:27. 移除元素
给你一个数组 nums
和一个值 val
,你需要 原地 移除所有数值等于 val
的元素,并返回移除后数组的新长度。
不要使用额外的数组空间,你必须仅使用 O(1)
额外空间并 原地 修改输入数组。
元素的顺序可以改变。你不需要考虑数组中超出新长度后面的元素。
/*
* @lc app=leetcode.cn id=27 lang=cpp
*
* [27] 移除元素
*/
// @lc code=start
class Solution {
public:
int removeElement(vector<int>& nums, int val) {
int slow = 0;
int fast = nums.size() - 1;
while (slow <= fast) {
while (slow <= fast && nums[slow] != val)
slow++;
while (slow <= fast && nums[fast] == val)
fast--;
if (slow < fast)
nums[slow++] = nums[fast--];
}
return slow;
}
};
// @lc code=end
题目二:26. 删除有序数组中的重复项
给你一个 非严格递增排列 的数组 nums
,请你 原地 删除重复出现的元素,使每个元素 只出现一次 ,返回删除后数组的新长度。元素的 相对顺序 应该保持 一致 。然后返回 nums
中唯一元素的个数。
考虑 nums
的唯一元素的数量为 k
,你需要做以下事情确保你的题解可以被通过:
- 更改数组
nums
,使nums
的前k
个元素包含唯一元素,并按照它们最初在nums
中出现的顺序排列。nums
的其余元素与nums
的大小不重要。 - 返回
k
。
写了一个相对位置版本的,不知道对不对~
/*
* @lc app=leetcode.cn id=26 lang=cpp
*
* [26] 删除有序数组中的重复项
*/
// @lc code=start
class Solution {
public:
int removeDuplicates(vector<int>& nums) {
int slow = 0;
int fast = nums.size() - 1;
int left = 1e5;
int right = 1e5;
while (slow <= fast) {
while (slow <= fast && nums[slow] != left && nums[slow] != right) {
left = nums[slow];
slow++;
}
while (slow <= fast && nums[fast] == right) {
fast--;
}
if (slow < fast){
right = nums[fast];
nums[slow++] = nums[fast--];
}
}
cout<<fast+1<<endl;
return fast+1;
}
};
// @lc code=end
int removeDuplicates(vector<int>& nums) {
int slow = 0;
int fast = 1;
while (fast <= nums.size() - 1) {
if (nums[fast] != nums[slow])
nums[++slow] = nums[fast];
fast++;
}
return slow+1;
}
题目三:283. 移动零
给定一个数组 nums
,编写一个函数将所有 0
移动到数组的末尾,同时保持非零元素的相对顺序。
请注意 ,必须在不复制数组的情况下原地对数组进行操作。
/*
* @lc app=leetcode.cn id=283 lang=cpp
*
* [283] 移动零
*/
// @lc code=start
class Solution {
public:
void moveZeroes(vector<int>& nums) {
int j = 0;
for (int i = 0;i < nums.size(); i++) {
if (nums[i] != 0)
nums[j++] = nums[i];
}
while (j < nums.size())
nums[j++] = 0;
}
};
// @lc code=end
题目四:844. 比较含退格的字符串
给定 s
和 t
两个字符串,当它们分别被输入到空白的文本编辑器后,如果两者相等,返回 true
。#
代表退格字符。
注意:如果对空文本输入退格字符,文本继续为空。
思路:
两个指针都从后往前移动
用 skip 记录 "#" 的个数
如果遍历是"#",i--,skip++;
如果遍历是字符,"skip>0",i--,skip--;
如果遍历是字符,"skip==0",比较;
/*
* @lc app=leetcode.cn id=844 lang=cpp
*
* [844] 比较含退格的字符串
*/
// @lc code=start
class Solution {
public:
bool backspaceCompare(string s, string t) {
int i = s.size() - 1;
int j = t.size() - 1;
int skips = 0;
int skipt = 0;
while (i >= 0 || j >= 0) {
while (i >= 0) {
if (s[i] == '#') {
skips++;
i--;
} else {
if (skips > 0) {
skips--;
i--;
} else {
break;
}
}
}
while (j >= 0) {
if (t[j] == '#') {
skipt++;
j--;
} else {
if (skipt > 0) {
skipt--;
j--;
} else {
break;
}
}
}
if (i >= 0 && j >= 0 && t[j] != s[i])
return false;
i--;
j--;
}
if (i != j)
return false;
return true;
}
};
// @lc code=end