算法训练营Day1
算法训练营Day1
报名迟了两天,今天补上第一天的内容
数组理论基础,704. 二分查找,27. 移除元素
题目
704. 二分查找
题目链接:https://leetcode.cn/problems/binary-search/
文章讲解:https://programmercarl.com/0704.%E4%BA%8C%E5%88%86%E6%9F%A5%E6%89%BE.html
视频讲解:https://www.bilibili.com/video/BV1fA4y1o715
思路
二分法模板题,强调有序数组
以前会在边界方面出问题,现在重点关注区间的定义,保证在区间内寻找target
有两种区间定义方式:
- 左闭右闭区间,也就是[left, right]
因为右闭所以初始化时要注意 r = nums.size() - 1
nums[mid] > target 时 ,升序所以taget在mid左侧,因为右闭所以mid值不符合,r = mid - 1
nums[mid] < target 时 ,升序所以taget在mid右侧,因为左闭所以mid值不符合,l = mid + 1 - 左闭右开区间,也就是[left, right)
因为右开所以初始化时要注意r=nums.size()
nums[mid] > target 时 ,升序所以taget在mid左侧,因为右开所以mid值符合(取不到),r = mid
nums[mid] < target 时 ,升序所以taget在mid右侧,因为左闭所以mid值不符合,l = mid + 1
代码
// 左闭右闭
class Solution {
public:
int search(vector<int>& nums, int target) {
int l = 0, r = nums.size() - 1;
while(l <= r){
long long mid = l + r >> 1;
if(nums[mid] > target) r = mid - 1;
else if(nums[mid] < target) l = mid + 1;
else return mid;
}
return -1;
}
// 左闭右开
class Solution {
public:
int search(vector<int>& nums, int target) {
int l = 0, r = nums.size();
while(l < r){
long long mid = l + r >> 1;
if(nums[mid] > target) r = mid;
else if(nums[mid] < target) l = mid + 1;
else return mid;
}
return -1;
}
};
27. 移除元素
题目链接:https://leetcode.cn/problems/binary-search/
文章讲解:https://programmercarl.com/0704.%E4%BA%8C%E5%88%86%E6%9F%A5%E6%89%BE.html
视频讲解:https://www.bilibili.com/video/BV1fA4y1o715
思路
题干中提到原地删除,所以不能使用额外的数组
- 暴力法
就是遍历数组,如果遇到val值,则将后面的值全都向前移动一格,但是要注意到当前位置的值已经被后一个值覆盖,所以要重新判断当前位置的值;
同时数据遍历的长度也要减小,因为整体左移一格,最后一个数字就没用了 - 双指针法
使用快慢指针,用慢指针s从0开始指向非val值在数组中的下标,用快指针f遍历数组,遇到非val值则赋值给慢指针s指向的下标,f>=s永远成立,所以不会出现非val值被覆盖的情况
代码
// 暴力法
class Solution {
public:
int removeElement(vector<int>& nums, int val) {
int n = nums.size();
int k = 0; // 统计val值的个数
int i = 0;
while (i < n - k) {
if (nums[i] == val) {
for (int j = i; j < n - 1 - k; j++) {
nums[j] = nums[j + 1];
}
k++;
} else
i++;
}
return n - k;
}
};
//题解的方法,感觉更容易理解
class Solution {
public:
int removeElement(vector<int>& nums, int val) {
int size = nums.size();
for(int i = 0; i < size; i ++){
if(nums[i] == val){
for(int j = i; j < size - 1; j ++){
nums[j] = nums[j + 1];
}
i --;
size --;
}
}
return size;
}
};
//双指针
class Solution {
public:
int removeElement(vector<int>& nums, int val) {
int n = nums.size();
int s = 0; //慢指针
for(int f = 0; f < n; ++ f){
if(nums[f] != val) nums[s ++] = nums[f];
}
return s;
}
};
977.有序数组的平方
题目链接:https://leetcode.cn/problems/squares-of-a-sorted-array/
文章讲解:https://programmercarl.com/0977.%E6%9C%89%E5%BA%8F%E6%95%B0%E7%BB%84%E7%9A%84%E5%B9%B3%E6%96%B9.html
视频讲解: https://www.bilibili.com/video/BV1QB4y1D7ep
思路
非递减顺序,即递增或者相等
双指针
自己的想法:既然是平方排序,那就是比较绝对值,所以首先找到数组中正负分开的那个下标,然后两个指针一个向左一个向右同时比较指针所指元素的绝对值大小,将绝对值小的数平方后放入新的数组中
题解思路:用两个指针从两端开始比较,将大的值存入新数组的末尾,从后往前存储;感觉比起从小到大存储代码更容易写而且简单
题干中提到原地删除,所以不能使用额外的数组
- 暴力法
就是遍历数组,如果遇到val值,则将后面的值全都向前移动一格,但是要注意到当前位置的值已经被后一个值覆盖,所以要重新判断当前位置的值;
同时数据遍历的长度也要减小,因为整体左移一格,最后一个数字就没用了 - 双指针法
使用快慢指针,用慢指针s从0开始指向非val值在数组中的下标,用快指针f遍历数组,遇到非val值则赋值给慢指针s指向的下标,f>=s永远成立,所以不会出现非val值被覆盖的情况
代码
// 贴上自己刚开始的代码
class Solution {
public:
vector<int> sortedSquares(vector<int>& nums) {
vector<int> r;
int i = 0;
//找到正负分开的位置
while(i < nums.size()){
if(nums[i] < 0) i ++;
else break;
}
if(i != nums.size()){
i --;
int j = i + 1;
while(i >= 0 && j < nums.size()){
if(abs(nums[i]) < abs(nums[j])){
r.push_back(nums[i] * nums[i]);
i --;
}
else{
r.push_back(nums[j] * nums[j]);
j ++;
}
}
while(i >= 0){
r.push_back(nums[i] * nums[i]);
i --;
}
while(j < nums.size()){
r.push_back(nums[j] * nums[j]);
j ++;
}
}
//如果遍历到头了,说明全是负数
else{
for(int i = nums.size() - 1; i >= 0; i --)
r.push_back(nums[i] * nums[i]);
}
return r;
}
};
//题解的方法,感觉更容易理解
class Solution {
public:
vector<int> sortedSquares(vector<int>& nums) {
int n = nums.size() - 1;
vector<int> res(nums.size());
int i = 0, j = n;
// i<=j 因为i==j的时候保存最后一个数字,所以不用i<j;也可以用n>=0代替
while(i <= j){
if(abs(nums[i]) > abs(nums[j])){
res[n --] = nums[i] * nums[i];
i ++;
}
else{
res[n --] = nums[j] * nums[j];
j --;
}
}
return res;
}
};
补充题目
35.搜索插入位置
题目链接:https://leetcode.cn/problems/search-insert-position/description/
思路
经典二分法,靠这个题目重温了一下y总的二分模板,又加深对二分的理解
查找第一个满足大于等于target的边界点
有两种写法,一种是特判所有数都小于target;
还有一种是取r=nums.size(),因为二分会返回范围内的点,如果范围只是[0,n-1]则不会返回n这个位置(target大于所有数的情况)
代码
class Solution {
public:
int searchInsert(vector<int>& nums, int target) {
int n = nums.size();
int l = 0, r = n - 1;
while(l < r){
int mid = l + r >> 1;
if(nums[mid] >= target) r = mid;
else l = mid + 1;
}
if(nums[n - 1] < target) return n; //特判
return l;
}
};
34. 在排序数组中查找元素的第一个和最后一个位置
题目链接:https://leetcode.cn/problems/find-first-and-last-position-of-element-in-sorted-array/description/](https://leetcode.cn/problems/find-first-and-last-position-of-element-in-sorted-array/description/)
思路
经典二分法
查找两个边界点,第一个满足大于等于target的边界点(开始位置),最后一个满足小于等于target的边界点(结束位置)
这里有一个点一直没注意,看了题解才发现,就是有一种情况是nums.size()为0,则直接返回-1,-1,如果数组访问就越界了
代码
class Solution {
public:
vector<int> searchRange(vector<int>& nums, int target) {
int n = nums.size();
if (n == 0)
return vector<int>{-1, -1};
vector<int> res;
int l = 0, r = n - 1;
// 查找开始位置
while (l < r) {
int mid = l + r >> 1;
if (nums[mid] >= target)
r = mid;
else
l = mid + 1;
}
res.push_back(l);
if (nums[l] != target)
return vector<int>{-1, -1};
// 查找结束位置
l = 0, r = n - 1;
while (l < r) {
int mid = l + r + 1 >> 1;
if (nums[mid] <= target)
l = mid;
else
r = mid - 1;
}
res.push_back(l);
return res;
}
};
补充内容
y总二分模板
代码
bool check(int x) {/* ... */} // 检查x是否满足某种性质
// 区间[l, r]被划分成[l, mid]和[mid + 1, r]时使用:
int bsearch_1(int l, int r)
{
while (l < r)
{
int mid = l + r >> 1;
if (check(mid)) r = mid; // check()判断mid是否满足性质
else l = mid + 1;
}
return l;
}
// 区间[l, r]被划分成[l, mid - 1]和[mid, r]时使用:
int bsearch_2(int l, int r)
{
while (l < r)
{
int mid = l + r + 1 >> 1; //防止死循环
if (check(mid)) l = mid;
else r = mid - 1;
}
return l;
}
思考
- 整数二分的本质根据要求将区间分成两个部分(一分为二),从左到右是一边满足一边不满足或者一边不满足一边满足(对应两个模板)
- 整数二分模板保证一定有解,二分其实是找到边界点,将区间一分为二
- check()函数用来检测每次mid值是否满足条件,根据是否满足来修改区间;如这里的条件是nums[mid]>=target是否成立
- 关注的是边界点而不是target!