一、基础知识
在c++中,数组元素在内存的地址是连续的,其它语言不一定,数组元素下标从0开始,只能被覆盖,不能被删除。
二、二分查找
前提条件:数组有序、数组所有元素唯一(否则返回下标不唯一);
问题:在数组中寻找值为target的元素下标,数组元素有序
思路:定义三个指针,左端指针left,右端指针right,中间指针middle;
1.不断将下标为middle的值与target比较,
2.若middle大,那么target一定在当前左区间,变right = middle-1;若target大,那么target一定在当前右区间,变left= middle+1;
3.重新计算middle,继续比较,
4.若找到值,返回下标,否则直到left>right,说明target不在这个数组中返回-1,
class Solution {
public:
int search(vector<int>& nums, int target) {
int left = 0;
int right = nums.size()-1;
int middle;
//注意点这个判断条件
while(left<=right)
{
//重新计算middle
middle = (left+ right)/2;
//做判断
if(nums[middle]<target)
{
left = middle+1;
}else if(nums[middle]>target)
{
right = middle-1;
}else if(nums[middle]==target)
{
return middle;
}
}
return -1;
}
};
注意点:
需要注意题目中要求的数组区间,一般有两种左闭右闭[1,2,3],左闭右开[1,2,3),若为左闭右闭那么当left == right时,right下标的值是可以取到的
例如在a[1,2,3]中寻找元素4过程:
1.left = 0,right = 2
2.middle = 1;target>a[middle];
3.left = middle+1 = 2;此时left == right由于是右闭区间,所以元素3可以被取到并判断。
结论:在左闭右闭区间中,判断条件为left<=right,且修改语句为left = middle+1;right=middle-1;
如果数组a为[1,2,3)那么上面的判断条件就是错误的,因为元素3取不到
结论:在左闭右开区间中,判断条件为left<right,且修改语句为left = middle+1;right= middle;
三、移除元素
问题: 给你一个数组 nums 和一个值 val,你需要 原地 移除所有数值等于 val 的元素,并返回移除后数组的新长度。
不要使用额外的数组空间,你必须仅使用 O(1) 额外空间并原地修改输入数组。
上面说过数组的删除 ,只能是覆盖掉它;
暴力解法:
思路:这个方法比较好想,就是循环遍历这个数组,发现要删除的数,就把后边的数全部向前移动一个位置(双循环)
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)
{
//向前移位 注意这个j的上限是size-1且取不到
for(int j=i;j<size-1;j++)
{
nums[j]=nums[j+1];
}
//注意size要改,i也要减一(踩坑处)
size--;
i=i-1;
}
}
return size;
}
};
上述代码的注意点:
第二层循环的终止条件是j<size-1;这和下面写的nums[j]=nums[j+1];是匹配的
然后就i=i-1;这个是因为所有元素前移之后,i位置上的元素已经变成后面的还没有判断的元素,若不加,则循环上面的i++操作,会直接跳过这个元素
解法二:快慢指针
思路:
1.定义两个指针,一个指针负责挨个遍历数组(快指针),一个指针负责记录已遍历并删除指定元素的子数组的末尾(慢指针)
2.如果快指针所指元素不是要删除元素,那么快慢指针是一起向后走的,一旦快指针遇到要删除元素了,慢指针就不在向后遍历(此时慢指针指向待删除元素),快指针继续向后走,指导找到第一个不要删除的元素
3.将其值,赋给慢指针(将要删除元素覆盖),慢指针向后移动,直到快指针遍历完毕,慢指针就是指向最终删除所有指定元素之后的数组的末尾
class Solution {
public:
int removeElement(vector<int>& nums, int val) {
int fast = 0;
int low = 0;
for(;fast<nums.size();fast++)
{
if(nums[fast]!=val)
{
nums[low]=nums[fast];//这里比较难想
low++;
}
}
return low;
}
};
注意点就是注释那里比较难想,了解了这个思路之后,再看就清晰多了
四、有序数组的平方
问题:给你一个按 非递减顺序 排序的整数数组 nums,返回 每个数字的平方 组成的新数组,要求也按 非递减顺序 排序。
思路:
暴力解法:
将数组的每一个元素求平方,然后利用sort()函数进行排序
class Solution {
public:
vector<int> sortedSquares(vector<int>& nums) {
for(int i=0;i<nums.size();i++)
{
nums[i]=nums[i]*nums[i];
}
sort(nums.begin(),nums.end());
return nums;
}
};
双指针解法:
分析:数组有序;平方后的结果的最大值要不在最左边,要不就是最右边;考虑双指针
思路:
1.定义两个指针分别指向数组两端,定义一个新数组存放结果
2.选取平方后结果大的存放入数组的末端(已知数组大小),并且将指针移到相邻的下个元素位置
3.直到所有元素判断完结束
class Solution {
public:
vector<int> sortedSquares(vector<int>& nums) {
//右端指针
int i = nums.size()-1;
//左端指针
int j = 0;
vector<int> result (nums.size(),0);
//判断条件是数组存满
for(int k=nums.size()-1;k>=0;k--)
{
//i,j所指结果
int ir = nums[i]*nums[i];
int jr = nums[j]*nums[j];
if(ir<=jr)
{
//存结果
result[k] =jr;
//移指针
j++;
}else{
result[k] = ir;
i--;
}
}
return result;
}
};
这个过程和归并排序操作有些类似。
四、长度最小的子数组
问题:给定一个含有 n 个正整数的数组和一个正整数 s ,找出该数组中满足其和 ≥ s 的长度最小的 连续 子数组,并返回其长度。如果不存在符合条件的子数组,返回 0。
思路:
暴力解法:
用双层循环,外层循环负责子字符串的起始位置指针变化,内层循环负责子字符串的最终位置指针变化。不断判断子字符串是否符合要求。算法复杂度为O(n^2)
class Solution {
public:
int minSubArrayLen(int target, vector<int>& nums) {
//记录每次判断的子字符串的长度
int length = 0;
//将每个过程中的最小值 初始化为无穷大值 方便后面的条件判断
int min = INT32_MAX;
int sum = 0;
for(int i=0;i<nums.size();i++)
{
//每次判断完一个子字符串之后 将sum重置0 不是i的值原因是下面循环j初始值为i
sum = 0;
for(int j=i;j<nums.size();j++)
{
length = j-i+1;
sum = sum+nums[j];
//如果符合条件 进入判断
if(sum>=target)
{
//这里就是初始化min为最大值的原因
if(length<min)
{
min = length;
}
}
}
}
//如果min没有改变 说明没有找到
if(min == INT32_MAX){
return 0;
}else
{
return min;
}
}
};
经典滑动窗口解法
这段内容转载思路写的很清晰,原文链接:https://blog.csdn.net/m0_68987535/article/details/130912679
基本思路
(1)初始化窗口:
初始化左右边界 left = right = 0,把索引闭区间 [left, right] 称为一个「窗口」。
(2)寻找可行解:
我们先不断地增加 right 指针扩大窗口 [left, right],直到窗口中的满足可行解。
(3)优化可行解:
此时,我们停止增加 right,转而不断增加 left 指针缩小窗口 [left, right],直到窗口中的可行解不再符合要求。同时,每次增加 left,我们都要更新一轮结果。
(4)滑动窗口,直至一次遍历结束:
重复第 2 和第 3 步,直到 right 到达到的尽头。
基本模板
public void slideWindowTemplate(String nums){
int l = 0, r = 0; //[初始化窗口]
//codes... [其他初始化信息,定义一些维护数据的数据结构]
while(r < nums.length){ //右边框移动
r++; //[增大窗口]
//codes..... [更新窗口中的数据]
while(l < r && check(xxx) == false){ //[窗口不满足某种性质]
l++; //[缩小窗口]
//codes... [维护窗口中的数据]
}
}
}
回归到这个问题,滑动窗口和暴力解法最大的区别就是滑动窗口可以通过不断操作左端的指针,来减少操作次数,使得每个元素在进入窗口时和从窗口删除时,共操作两次,操作次数为2*n,复杂度为O(n)
class Solution {
public:
int minSubArrayLen(int target, vector<int>& nums) {
//存储结果
int result = INT32_MAX;
//存储每一次计算的和
int sum= 0;
int length =0 ;
//初始化左端指针
int i = 0;
for(int j = 0;j<nums.size();j++)
{ //不断向后遍历,并把遍历过得元素加入到这个结果中
sum= sum+nums[j];
length++;
//符合条件 就调整i(左端指针的值)
while(sum>=target)
{
if(result>length)
{
result = length;
}
//i往前移动 sum减去移除元素 长度变化
sum = sum-nums[i];
length--;
i++;
}
}
if(result == INT32_MAX)
{
return 0;
}else{
return result;
}
}
};
本文依托于代码随想录原文链接https://programmercarl.com/