代码随想录算法训练营第一天 | LeetCode704.二分查找、LeetCode27.移除元素、LeetCode977.有序数组的平方
01 理论基础
- ref:数组:连续内存空间
- 要点:下标本从0开始、内存空间连续、C++如果使用
vector<int>
实现数组,其在调用erase时复杂度为O(n)
02-1 LeetCode704.二分查找
相关资源
题目链接: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
题目:给定一个 n
个元素有序的(升序)整型数组 nums
和一个目标值 target
,写一个函数搜索 nums
中的 target
,如果目标值存在返回下标,否则返回 -1
。
第一想法:因为本科的时候系统学习过算法设计课程,也有一定的C语言编程经验,因此对二分法是比较熟悉的,这种二分查找法适用于已经排好序的数组,通过O(logn)复杂度即可查找到目标。
实现:理想很丰满,现实很骨感,在实现过程中遇到while条件和边界赋值两大难关,借助Debug逐渐理清思路,我采用的其实是左开右开的区间定义,这样边界无脑用middle更新即可。最初写的代码如下:
#include<iostream>
#include <limits>
#include<vector>
using namespace std;
class Solution {
public:
int search(vector<int>& nums, int target) {
int i = 0;
int j = nums.size()-1;
int result = -1;
bool flag = false;
// 最开始就要判断边界以获得开区间
if (nums[i] == target) {
result = i;
flag = true;
}
else {}
if (nums[j] == target) {
result = j;
flag = true;
}
else {}
// while循环中结束条件设置为 i < j - 1
// 因为(i,i+1)其实就已经没有数了,即可跳出循环
while (i < j-1) {
// 听代码随想录之前其实脑子里没有循环不变量的意识,其实下面两行代码可以优化掉
if (nums[i] == target) {
result = i;
flag = true;
break;
}
else {}
if (nums[j] == target) {
result = j;
flag = true;
break;
}
else {}
int middle = (j + i) / 2;
// 边界大胆赋值middle即可
if (nums[middle] < target) {
i = middle;
}
else if(nums[middle] == target) {
result = middle;
flag = true;
break;
}
else {
j = middle;
}
}
if (flag) {
return result;
}
else {
return -1;
}
};
};
int main(){
vector <int> nums { -1,0,3,5,9,12 };
int target = 9;
Solution solution;
int result = solution.search(nums, target);
cout << result << endl;
return 0;
}
看完代码随想录之后的想法: 写算法题不能一股脑就是干,要多想想,因为逻辑不太清晰,初次写的代码有很多步骤是完全不需要的,有待优化
收获
-
明确二分法的循环不变量规则,即每次循环区间定义保持一致
-
更常见的区间定义为左闭右闭、左开右闭
ToDo:尝试优化左开右开、尝试左闭右闭、左闭右开
02-2 LeetCode27.移除元素
相关资源
题目链接:https://leetcode.cn/problems/remove-element/
文章讲解:https://programmercarl.com/0027.%E7%A7%BB%E9%99%A4%E5%85%83%E7%B4%A0.html
视频讲解:https://www.bilibili.com/video/BV12A4y1Z7LP
题目:
给你一个数组 nums
和一个值 val
,你需要原地移除所有数值等于 val
的元素。元素的顺序可能发生改变。然后返回 nums
中与 val
不同的元素的数量。
假设 nums
中不等于 val
的元素数量为 k
,要通过此题,您需要执行以下操作:
- 更改
nums
数组,使nums
的前k
个元素包含不等于val
的元素。nums
的其余元素和nums
的大小并不重要。 - 返回
k
。
第一想法:之前略微了解过双指针,就没有太往暴力双循环去考虑。之前接触过的双指针通常是首指针和尾指针,然后逐渐向之间逼近,秉持此思维定势,设计算法:把尾指针不等于val
的元素和首指针等于val
的元素交换,等到汇合的时候即达到目标。
实现:
#include<iostream>
#include <limits>
#include<vector>
using namespace std;
class Solution {
public:
int removeElement(vector<int>& nums, int val) {
// 首指针
int i = 0;
// 尾指针
int j = nums.size()-1;
// 计数
int sum = 0;
while (i <= j) {
// 从前往后找到第一个等于待删除数
if (nums[i] != val) {
i = i + 1;
sum = sum + 1;
}
else {
// 找到第一个等于待删除数,从后往前找到第一个非删除数和之前待删除数交换
if (nums[j] != val) {
nums[i] = nums[j];
nums[j] = val;
i = i + 1;
sum = sum + 1;
}
else {}
j = j - 1;
}
}
return sum;
}
};
int main() {
vector <int> nums{ 0,1,2,2,3,0,4,2 };
int target = 2;
Solution solution;
solution.removeElement(nums, target);
cout << nums[2]<< endl;
return 0;
}
疑问:C++调用函数的时候会直接修改原始的数组?我还以为会涉及到浅拷贝或者深拷贝,待进一步钻研。
看完代码随想录之后的想法: 可以采用快慢指针来实现,思维复杂度更低
收获
-
双指针还有快慢指针,不一定是首尾指针,但均一次遍历即可达到目标。快指针:寻找新数组的元素,慢指针:指向更新新数组下标的位置
-
C++如果使用
vector<int>
实现数组,其在调用erase底层机制就是这样,复杂度为O(n)
ToDo:尝试快慢指针
02-3 LeetCode977.有序数组的平方
相关资源
题目链接: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
题目:给你一个按 非递减顺序 排序的整数数组 nums
,返回 每个数字的平方 组成的新数组,要求也按 非递减顺序 排序。
第一想法:经典首尾双指针,设计算法:把尾指针元素和首指针元素平方进行比较,大的放在新数组的尾端,等到汇合的时候即达到目标。
实现:
#include<iostream>
#include <limits>
#include<vector>
using namespace std;
class Solution {
public:
vector<int> sortedSquares(vector<int>& nums) {
int i = 0;
int j = nums.size() - 1;
vector<int> sort = {};
int square_left;
int square_right;
while (i <= j) {
square_left = nums[i] * nums[i];
square_right = nums[j] * nums[j];
// 两端中较大的插入
if (square_left >= square_right) {
i = i + 1;
sort.insert(sort.begin(), square_left);
}
else {
j = j - 1;
sort.insert(sort.begin(), square_right);
}
}
return sort;
};
};
int main() {
vector <int> nums{ -7,-3,2,3,11 };
Solution solution;
vector<int> new_nums = solution.sortedSquares(nums);
return 0;
}
看完代码随想录之后的想法: 算法设计上和代码随想录相同,但具体实现采用了库函数的insert,导致资源占用加大
收获:首尾指针
ToDo:预定义一个等大的数组,然后从后往前赋值以减少资源占用