🔥博客主页: A_SHOWY
🎥系列专栏:力扣刷题总结录 数据结构 云计算
跟随代码随想录初刷力扣算法,语言全部用c++,记录总结
704. 二分查找 | easy | 二分法 |
35. 搜索插入位置 | easy | 二分法 |
34. 在排序数组中查找元素的第一个位置和最后一个位置 | mid | 二分法 |
69. X的平方根 | easy | 二分法 |
367. 有效的完全平方数 | easy | 二分法 |
27. 移除元素 | easy | 双指针 |
26. 删除有序数组中的重复项 | easy | 双指针 |
283. 移动0 | easy | 双指针 |
844. 比较含退格的字符串 | easy | 双指针 |
977. 有序数组的平方和 | easy | 双指针 |
209. 长度最小的子数组 | mid | 滑动窗口 |
904. 水果成篮 | mid | 滑动窗口 |
59. 螺旋矩阵2 | mid | 螺旋矩阵 |
54. 螺旋矩阵 | mid | 螺旋矩阵 |
一、数组基础理论
1.数组是存放在连续空间的相同类型数据的集合
-
数组下标都是从0开始的。
-
数组内存空间的地址是连续的
2.要注意vector 和 array的区别,vector的底层实现是array,严格来讲vector是容器,不是数组
3.数组的元素是不能删的,只能覆盖。 在C++中二维数组是连续分布的
二、二分查找
(1)704. 二分查找
704. 二分查找https://leetcode.cn/problems/binary-search/
class Solution {
public:
int search(vector<int>& nums, int target) {
int left = 0;
int right = nums.size() - 1;//这里定义target在一个左闭右开区间
while (left <= right)//所以这里等于的时候有意义
{
int mid = left + ((right - left)/2);//注意这里防止溢出
if(target < nums[mid])
{
right = mid - 1;
}
else if( nums[mid] < target)
{
left = mid + 1;
}
else {return mid;}
}
return -1;
}
}
为了方便记忆,可以一律统一使用左闭右闭经典二分法
(2)35. 搜索插入位置
35. 搜索插入位置https://leetcode.cn/problems/search-insert-position/
//其实可以直接套用二分法,然后在小的地方插入,target小于mdiddle时,借助ans
class Solution {
public:
int searchInsert(vector<int>& nums, int target) {
int left = 0;
int right = nums.size() -1;
int ans = nums.size();
while (left<= right)
{
int middle = ((left+right)/2);
if(nums[middle]>=target)
{
ans = middle;
right = middle-1;
}
else
{
left = middle+1;
}
}
return ans;
}
};
经典的二分法问题,多考虑一下当nums[mid] >= target 的时候,让ans(基准)移动到mid。
(3)34. 在排序数组中查找元素的第一个位置和最后一个位置
class Solution {
public:
vector<int> searchRange(vector<int>& nums, int target) {
int lborder = getLborder(nums,target);
int rborder = getRborder(nums,target);
//情况1
if(lborder == -2 || rborder == -2) return{-1,-1};
if((rborder -lborder) >1) return{lborder+1,rborder-1};
return{-1,-1};
}
int getLborder(vector<int>& nums,int target)
{
int left = 0;
int right = nums.size() - 1;
int lborder = -2;
while(left <= right)
{
int mid = (left +(right-left)/2);
if(nums[mid] < target)
{
left = mid + 1;
}
else{right = mid -1;
lborder = right;
}
}
return lborder;
}
int getRborder(vector<int>& nums,int target)
{
int left = 0;
int right = nums.size() - 1;
int rborder = -2;
while(left <= right)
{
int mid = (left +(right-left)/2);
if(nums[mid] > target)
{
right = mid -1;
}
else
{
left = mid + 1;
rborder = left;
}
}
return rborder;
}
};
思路:要用到两个二分法寻找左右边界,核心在于更新边界在等于的时候
要考虑到三种情况:
1、target在左边或者右边
2、target在之中但是不存在
3、target在其中并且存在。
为了寻找这三种情况,需要用两次二分法寻找左右边界。
(4)69. X的平方根
69. x 的平方根 https://leetcode.cn/problems/sqrtx/
class Solution {
public:
int mySqrt(int x) {
int left = 0;
int right = x;
int ans = -2;
while(left<=right)
{
int mid = left+((right-left)/2);
if((long)mid * mid >= x)
{
left = mid + 1;
ans = left;
}
else {right = mid-1;}
}
return ans;
}
};
二分法,把平方根在0到x进行二分使其满足平方<x
(5)367. 有效的完全平方数
367. 有效的完全平方数https://leetcode.cn/problems/valid-perfect-square/
class Solution {
public:
bool isPerfectSquare(int num) {
int left = 0;
int right = num;
int ans = -2;
while (left <= right)
{
int mid = (left+(right-left)/2);
long square = (long)mid*mid;
if(square < num)
{
left = mid + 1;
}
else if (square > num)
{
right = mid -1;
}
else return true;
}
return false;
}
};
二分法,看到平方可以考虑二分法
三、移除元素
使用erase库函数时,时间复杂度是o(n)双指针法
双指针法(快慢指针法): 通过一个快指针和慢指针在一个for循环下完成两个for循环的工作。
定义快慢指针
-
快指针:寻找新数组的元素 ,新数组就是不含有目标元素的数组
-
慢指针:指向更新 新数组下标的位置
(1)27. 移除元素
27. 移除元素https://leetcode.cn/problems/remove-element/
class Solution {
public:
int removeElement(vector<int>& nums, int val) {
int sn = 0;
for(int fn = 0;fn<nums.size();fn++)
{
if(nums[fn]!= val)
{
nums[sn] = nums[fn];
sn++;
}
}
return sn;
}
};
思路:让快指针走,遇到不要的元素就跳过,遇到要的就给慢指针
(2)26. 删除有序数组中的重复项
26. 删除有序数组中的重复项https://leetcode.cn/problems/remove-duplicates-from-sorted-array/
class Solution {
public:
int removeDuplicates(vector<int>& nums) {
int slown = 0;
for(int fastn =0;fastn<nums.size();fastn++)
{
if(nums[fastn]!=nums[slown])
{
nums[++slown] = nums[fastn];
}
}
return slown+1 ;
}
};
思路同上题基本一模一样,练习
(3)283. 移动0
283. 移动零https://leetcode.cn/problems/move-zeroes/
class Solution {
public:
void moveZeroes(vector<int>& nums) {
int slow = 0;
for(int fast = 0;fast < nums.size();fast++)
{
if(nums[fast] != 0)
{
nums[slow] = nums[fast];
slow++;
}
}
for(int k =slow ;k< nums.size();k++)
{
nums[slow] = 0;
slow++;
}
}
};
思路:双指针把0去掉,再循环加0 。和27的思路一致
(4)844. 比较含退格的字符串
844. 比较含退格的字符串https://leetcode.cn/problems/backspace-string-compare/
class Solution {
public:
bool backspaceCompare(string s, string t) {
int t1 = 0;
int t2 = 0;
int i = s.length() - 1;
int j = t.length() - 1;
//先把所有的#相关的去掉
while(1)
{
while(i >= 0)
{
if( s[i] == '#')
{
t1++;
}
else
{
if(t1 > 0) t1--;
else break;
}
i--;
}
while(j >= 0)
{
if( t[j] == '#')
{
t2++;
}
else
{
if(t2 > 0) t2--;
else break;
}
j--;
}
if(i<0 || j<0) break;
if(s[i] != t[j]) return false;
i--;j--;
}
if(i==-1 && j==-1) return true;
return false;
}
};
核心思路:核心是先把#的前面的字符去掉,然后再使用双指针逐个比较
(5)977. 有序数组的平方和
977. 有序数组的平方https://leetcode.cn/problems/squares-of-a-sorted-array/
1.暴力算法(直接排序)
class Solution {
public:
vector<int> sortedSquares(vector<int>& nums) {
for(int i = 0;i<nums.size();i++)
{
nums[i] *= nums[i];
}
sort(nums.begin(),nums.end());
return nums;
}
};
2.双指针方法
class Solution {
public:
vector<int> sortedSquares(vector<int>& nums) {
int n = nums.size();
vector<int> ans(n);
for(int i = 0,j = n-1, p= n-1; i<=j;)
{
if(nums[i]*nums[i]<=nums[j]*nums[j])
{
ans[p] = nums[j] * nums[j];
j--;
}
else
{
ans[p] = nums[i] * nums[i];
i++;
}
p--;
}
return ans;
}
};
暴力:直接用sort快速排序
双指针:用双指针,左右都是最大的,把最大的倒叙放入一个新的数组中
四、长度最小的子数组(滑动窗口,核心也是双指针)
(核心是移动起始位置)
滑动窗口: 不断的调节子序列的起始位置和终止位置,从而得出我们要想的结果
(1)209. 长度最小的子数组
209. 长度最小的子数组https://leetcode.cn/problems/minimum-size-subarray-sum/
class Solution {
public:
int minSubArrayLen(int target, vector<int>& nums) {
int sum = 0;//定义数组和
int result = INT32_MAX;
int i = 0;//定义起始指针
int length = 0;//定义滑动窗口长度
for(int j = 0;j<nums.size();j++)
{
sum += nums[j];
while(sum >= target)
{
length = (j-i+1);
result = result < length ? result : length;
sum -= nums[i++];
}
}
return result == INT32_MAX ? 0 :result;
}
};
while (sum >= s) {
subLength = (j - i + 1); // 取子序列的长度
result = result < subLength ? result : subLength;
sum -= nums[i++]; // 这里体现出滑动窗口的精髓之处,不断变更i(子序列的起始位置)}
补充:INT32_MAX
(2)904. 水果成篮
904. 水果成篮https://leetcode.cn/problems/fruit-into-baskets/
class Solution {
public:
int totalFruit(vector<int>& fruits) {
int l= 0;
int r =0;
int ans = 2;
//定义两个篮子
int ln = fruits[l];
int rn = fruits[r];
while(r < fruits.size())
{
if(fruits[r] == rn||fruits[r] == ln )
{
ans = max (r-l+1,ans);
r++;
}
//滑动
else//当第三种出现
{
l =r - 1;
ln = fruits[l];//这时候第三种就做rn
while (l>=1 && fruits[l-1] == ln) l--;
rn = fruits[r];
ans = max (ans,r-l+1);
}
}
return ans;
}
};
滑动窗口,如何移动起始指针,是直接找到r,然后慢慢往回找
可以用到哈希表(复刷一遍)
五、螺旋矩阵(模拟)
是一个模拟的过程,注意左开右闭(循环不变量原则)
(1)59. 螺旋矩阵2
59. 螺旋矩阵 IIhttps://leetcode.cn/problems/spiral-matrix-ii/
class Solution {
public:
vector<vector<int>> generateMatrix(int n) {
//定义一个n*n 的二维数组
vector<vector<int>> res(n,vector<int>(n,0));
//定义起始位置
int startx = 0;
int starty = 0;
int loop = n/2;//圈数
int offset = 1;//补偿数量
int count = 1;
int mid = n/2;
int i,j;
while(loop--)
{
i = startx;
j = starty;
//从左到右
for(j = starty; j < n-offset; j++)
{
res[i][j] = count++;
}
//从上到下
for(i = startx; i < n-offset; i++)
{
res[i][j] = count++;
}
//从右到左
for(;j>starty; j--)
{
res[i][j] = count++;
}
//从下到上
for(;i>startx; i--)
{
res[i][j] = count++;
}
//转第二圈的变化
startx++;
starty++;
offset++;
}
if(n % 2)
{
res[mid][mid] = count;
}
return res;
}
};
1.四个循环模拟的过程需要注意
2. 定义二维数组n*n的形式 vector<vector<int>> res(n,vector<int>(n,0));
3.考虑边长是奇数个时,考虑mid
(2)54. 螺旋矩阵
54. 螺旋矩阵https://leetcode.cn/problems/spiral-matrix/
class Solution {
public:
vector<int> spiralOrder(vector<vector<int>>& matrix) {
//判断行列矩阵是否为空
if(matrix.size() == 0 || matrix[0].size() == 0) return {};
int rows = matrix.size();int cols = matrix[0].size();
int n = rows * cols;
vector<int> res(n);//定义一个一维数组存放结果
int startx = 0,starty = 0;
int loop = min(rows,cols) / 2;
int mid = min(rows,cols) / 2;
int i,j;
int offset = 1;
int count = 0;
while(loop--)
{
i = startx;
j = starty;
//从左向右
for(j = starty; j < cols - offset; j++)
{
res[count++] = matrix[i][j];
}
//从上到下
for(i = startx; i < rows - offset; i++)
{
res[count++] = matrix[i][j];
}
//右到左
for(; j > starty; j--)
{
res[count++] = matrix[i][j];
}
//从下到上
for(; i > startx; i--)
{
res[count++] = matrix[i][j];
}
startx++;
starty++;
offset++;
}
if(min(cols,rows) % 2)
{
if(rows > cols){
for(int i = mid; i < mid + rows - cols + 1; ++i){
res[count++] = matrix[i][mid];
}
}
else {
for(int i = mid; i < mid + cols - rows + 1; ++i){
res[count++] = matrix[mid][i];
}
}
}
return res;
}
};
和上一题一样都为模拟的过程,但是又区别
1.圈数不一样,这个题目为min(cols,rows)/2;//rows是行数,cols是列数
2.mid不一样
3.考虑的特殊情况不一样,此题当min(cols,rols)为奇数时,需考虑剩下的部分数值如何填充
4. if(matrix.size() == 0 || matrix[0].size() == 0) return {};//判断矩阵是否为空的操作