1.双指针
总结:
1.复写0这道题,告诉我们要正难其反,我们从后向前进行重写,删除某些数字的时候,我们可以从前向后遍历,但是增加一些数字的时候会对后面的数据进行覆盖,所以要从后向前进行
2.快乐数涉及到环的题目第一就要想到快慢双指针
3. 1.3到1.7这几道题其实都是利用了单调性的性质,在暴力枚举的基础上,节省了时间复杂度
1.1.复写0
解题思路:
1.这道题遇到0就会向后进行覆写操作,所以我们按照正常的思路从前向后便利的时候,
遇到0,进行覆写,就会将0后面的数字进行覆盖,造成数据的丢失
所以我们要正难其反,我们从后向前进行重写,
所以我们就引出了第二个问题,如何在数组中找到变型后的最后一位数字
2.如何在数组中找到变型后的最后一位数字,定义两个指针,cur和ret,ret起始为-1,cur为0,cur从前向后进行遍历,arr【cur】==0,ret+=2;反之 ret+=1;
代码实现:
注意点:
1.ret判断的结束条件一定在cur++的前面
2.边界情况
当我cur指向的最后一个数字是0的时候,ret是连休跳两格,所以要处理边界情况
1.2.快乐数
解题思路:
1.我们都会发现,给定的数都会最后进入一个循环当中,要么是一个不包括数字1的循环,要么是一个只包括数字1的循环
2.涉及到环的题目第一就要想到快慢双指针,fast和last fast一次走两步,last一次走一次,最后一定会相遇,此时相遇以后跳出循环,判断2遇到的值是否是1即可
代码实现:
1.3.盛最多水的容器
解题思路:
1其实这道题要要使用单调性来做,定义两个指针,一个是left左区间,right右区间
假设此时left在0下标,right在n-1下标,
那么此时的 V=L*H
此时我的L是最大的,但是我的高度是以较小值为基准,
若此时 以最小值的高度和其他下标进行匹配,只会出现以下两种情况
V=L(减小)* H(不变);
V=L(减小) *H(减小);
所以我们此时如果还用较小的高度和其他下标进行匹配,结果一定是变小的,
所以此时我们可以直接跳过跳过较小的一方,
这样循环,直到left==right结束
代码实现:
1.4.有效三角形的个数
解题思路:
判断三条边是否能构成三角形,只需判断 A+B>C(最大的边)
所以就转换成在一个数组中固定一个最大的数,再找其他两条边的问题
所以此时我们就要想到使用单调性的性质,
假设此时是一个有序的数组,
我们从后向前依次固定最大数然后在最大值的左区间找匹配的另外两条边即可
此时有两种情况:
情况一:A+B>C:因为数组是有序的,所以A下标到B下边-1的区间的数据都是符合条件的,
算出区间的大小进行累加,再将第二大的边进行--;即可
情况二:A+B<C:A向前走一步即可
代码实现:
1.5.和为 s 的两个数字
代码实现:
1.6.三数之和
解题思路:
其实思路和之前的求有效个数的三角形是一样的,从后往前依次固定一个数,再在其左区间找到符合条件的另外两个数,也是也运用了单调性的性质
代码实现:
注意点:
此时的要求是不能有重复的数据组
1.当遇到符合条件的数据组的时候,此时的left要向前寻找一个新的值,right要向后寻找一个新的值
此时还要主意一点就是注意数组越界的问题
2.当一个固定的值结束以后,固定值也是要跳过相同的值,寻找新的值
此时就不可以两边都对i进行移动,防止错过合适的数据组
1.7.四数之和
解题思路:
代码的思路其实就是在三数之和的基础上多套一层,多了一个固定的数而已
代码实现:
注意点:
注意点1:依旧是 去重+防止越界
注意点二:数据太大,改成long long类型
2.滑动窗口
总结:
使用场景:
当题目给定一个数组,让你在数组中寻找的符合条件的子区间,此时我们就可以使用滑动窗口来解决这道问题。
滑动窗口的行为:定义两个指针left和right,分别表示子区间的区间和右区间,两个指针都是从前向后进行移动,直到右区间超出了数组的范围才结束
所以可以到得出一个结论,这个数组一定是单调的(这里的单调并不是升序,降序的表面含义)当right向右走(范围扩大)离我们要符合的目标值就更进一步,当left向右走(范围变小)离我们要符合的目标值就更远一步,
分类:
滑动窗口分成动态的滑动窗口(大小可变)和动态的滑动窗口(大小不变)
2.6是静态的,其余都是动态的
2.1.长度最小的子区间
解题思路:
1.其实我们一开始想到的思路就是,我定义两个指针 left 和right 将数组中所有的子数组都列举出来,就可以找到最小的长度,但是一定会超出时间限制
2.此时我们就想想,在哪里可以做出一些优化,
ret:长度
sum:记录区间的值
left:左区间
right:右区间
第一点:数组都是正数,只要区间变大(right向右移动),sum和就一定会变大,反之(left向右移动)变小,
所以我们会遇到两种情况,
sum<target:right向右移动,时sum变大
sum>=target:left位置给去除,left++,此时right从left从新开始记录
注意:此时要优化的点就是right不需要再次重新从left位置进行记录,
right再次重新从left位置向前走,无非时再次记录重复的区间
所以
当left位置给去除,left++,
又会出现两种情况
sum>=target:再次进行更新即可
sum<target:继续寻找符合要去的区间,right向右移动
代码实现:
细节注意:
1.求最小值,一开始就定义最大值,返回阶段要对结果进行判断
2.缩小左区间的时候,要一直缩小到sum重新小于目标值为止,记得更新右区间
2.2.无重复字符的最长字串
解题思路:
这道题和上道题很相似,统计子串的最长的长度,所以同向的双指针可以解决这道问题
代码实现:
细节注意:
1.数组模拟hash表
2.代码的执行顺序
1.先更新右窗口
2.判断是否符合条件
3.更新长度
原因:在不断更新右区间的过程,有两种情况
1.加入后依旧不重复,不用更新left,直接更新结果
2.加入后重复,更新left,更新结果也没事,此时找的时最大的区间,left向右移动,一定是减小长度,len不会被影响
3.如果不符合条件的话,下一次left的起始位置时重复的下一个字符
4.判断是否符合条件时,判断新加入的字符映射的hash表的个数是否已经大于1
2.3.最大连续1的个数
解题思路:
此时我们要将此题目进行转变成
在数组中找到一个最大的子区间,其中子区间的中0的个数不能超过k
此时就是我们熟悉的找到一个最长的连续子区间,利用单调性,使用滑动窗口来解决此题目·
代码实现:
2.4.将减到0的最小操作数
解题思路:
正难则反:题目要我们在最左边和最右边找到两个区间,这俩区间的值加起来等于目标值,
我们就可以转换成,在数组中找到一个子区间,这个区间的值等于count-x,但是要这个区间的长度最长的问题
代码实现:
细节注意:
1.边界问题:
count-x=负数:不存在,直接返回-1
count-x==0:直接返回数组的大小即可
2.凑不出的情况:
2.5.水果成蓝
解题思路:
其实这道题的意思就是找到一个最长的子区间,其中的种类不能超过两种,
同向双指针,滑动窗口
代码实现:
细节:
容器的使用:
1.种类是否超出限制 使用size()
2.删除种类,使用 erase
2.6.找到字符串中所有字母异位词
解题思路:
这个滑动窗口的思路比之前的更加的明显,因为此时窗口的大小是不变的,一直以p,size()的大小一直向后进行移动
代码实现:
细节:
1.中间有个步骤比较两个hash1和hash2是否相同,相同的话就是符合条件反之不符合
但是如果我们是对两个hash表进行遍历,来进行比较,效率太低了
此时我们定义一个count值,count出现的地方右三处,用于记录有效字符的个数
1.在进窗口的时候
hash2【字符】小于hash【字符】的个数的话,count就++
2.在出窗口的时候
hash2【字符】小于等于hash【字符】的个数的话,count就--
3.判断区间是否是合法区间
count==给定字符串的个数,那么此时就是合法区间
2.7.串联所有单词的子串
解题思路:
这道题其实是可以转换成找到字符串中所有字母异位词
我们只需要将将words[i].length 个字符看作成一个整体
代码实现:
细节处理:
1.根据words
中所有字符串 长度相同,而且字符串 s是由words
中的单词所组成,所以我们就可以这道题转化成找到字符串中所有字母异位词来做,
大体的的要循环words
字符串 长度,
2.left和right下标的移动也是以一个len为一个单位进行移动
3.使用字符串容器接口,取指定位置的子字符串
2.8.最小的覆盖子串
解题思路:
和之间一样定义两个双指针,从左往右进行遍历,,不断的更新最小的区间
代码实现:
细节处理:
1.此时的count不是和之前记录有效字符的个数,而是记录有效字符的种类
因为之前题目的子区间中的字符个数个数是一 一对应的,但是此时假设我原数组中只有一个A,
但是我子区间有两个A,这样的子区间也是符合条件的
3.二分查找
总结:
是否能够使用二分查找,看看数组是否具有二段性,再进行判断是是使用哪个二分模版
使用情景:
当数据具有二段性的时候我们就可以使用二分查找的算法,此时的二段性并不是代表数组有序的意思。
在3.1~3.4中,数组是有序是,所以此时他们的二段性是:小于更新左区间,大于更新右区间
3.5和3.6,数组并不有序,但是它的二段性是:前一个小于后一个更新左区间,前一个大于后一个更新右区间
3.7中,也是无序的,但是二段性是找出一个基准值:大于基准值更新左区间,小于基准值更新右区间
3.8中,的二段性是根据index和arr【index】是否相等来确定
模版分类:
朴素二分:
朴素二分,就是将具有二段性的数组三部分:小于target, 等于taeget ,大于target
注意点:循环结束条件:left<right, 每次的移动:left=mid+1; 和 right=mid-1;
查找区间左端点模版:
找到符合条件的最左边的位置
此时就是将数组分成 小于target 和 大于等于target 两部分,
mid落在 小于target的时候,left=mid+1;left是一直在试图跳出不符合这个target的区间,
mid落在 大于等于target的时候,right=mid;right只能不断的靠近左区间,并不会跳出这个区间
注意:
1.循环的结束条件:left<right;当left==right的时候就是最后的结果
2.mid=left+(right-left)/ 2;个数是偶数的时候,mid位置靠左,防止死循环
查找区间右端点模版:
找到符合条件的最右边的位置
此时就是将数组分成 小于等于target 和 大于target 两部分,
mid落在 大于target的时候,right=mid+1;right是一直在试图跳出不符合这个target的区间,
mid落在 小于等于target的时候,right=mid;right只能不断的靠近右区间,并不会跳出这个区间
注意:
1.循环的结束条件:left<right;当left==right的时候就是最后的结果
2.mid=left+(right-left+1)/ 2;个数是偶数的时候,mid位置靠右,防止死循环
3.1.二分查找
解题思路:
这道题就是经典的朴素的二分算法
代码实现:
细节分析:
3.2.在排序数组中查找元素的第一个和最后一个位置
解题思路:
先将数组分成 小于target 和 大于等于target 两部分,找到最左边的值,
再将数组分成 大于target 和 小于等于target 两部分,找到最右边的值
代码实现:
细节分析:
1.处理边界情况:
2.不存在的情况,要对下标进行验证
3.3.搜索插入的位置
解题思路:
如果目标值都存在的话就是,就使用朴素的二分算法,
但是此时有可能target这个值不存在就需要返回,如果存在本该插入的位置,也就是大于target最近的位置,
所以此时我们就可以将数组分成 小于target 和 大于等于target 两部分,找到最左边的值,
代码实现:
细节:
当目标值不存在,并且比所有的值都要小的时候,left和right会停在起始位置,是我们最终的结果,不需要特殊处理,但是如果此时目标值比所有的值都要大,left和right停在n-1的位置,但是插入的位置是n,所以对这需要特殊处理一下
3.4.x的算术平方根
解题思路:
要求x的算术平方根,暴力的解决方法就是,从0向后一直进行遍历,直到一个数的平方,大于x,
所以我们就可以找到一个区间,1~x,这个区间,将这个区间分成,小于等于target 和 大于target 两部分,进行二分查找
代码实现:
细节处理:
1.处理0
2.换成 long long 扩大范围
3.5.山脉数组的峰顶索引
解题思路:
并不是说数组有序才能使用二分查找算法,只要数组具有二段性,我们就可以使用二分查找算法,
下面这个数组就是天然的二段性,前面的 arr[i]>arr[i-1] ,后面的 arr[i]<arr[i-1]
所以
就将数组分成arr[i]>arr[i-1] , arr[i]<arr[i-1] 两部分,并寻找arr[i]>arr[i-1] 的最右区间
代码实现:
3.6.寻找峰值
解题思路:
这道题和上道题的唯一的一个差异就是,上道题只有一个峰值,这道题有多个峰值
其实都一样,找出其中的二段性,选择 mid 和mid-1的位置
1.当 nums[mid-1]<nums[mid]:右边的区间一定有峰值,left=mid;
2.当 nums[mid-1]>nums[mid]:左边的区间一定有峰值,right=mid-1;
代码实现:
代码的实现其实和上道题一模一样
3.7.寻找旋转排序数组中的最小值
解题思路:
c是我们要找的值,
此时我们以D位置的值为基准点,A~B区间的值都比D要大,C~D都是小于等于D,
所以此时这就是是我们要寻找的二段性,
1.mid落在A~B区间 ,left=mid+1;
2.mid落在C~D区间 ,right=mid;
代码实现:
3.8.点名
解题思路:
可以发现此时的二段性是与数组的下标是有关系的,
在没有缺失数字之前,index==arr[index],是相同的,反之后面便不是对应的
根据此时的二段性,
1.mid落在对应的区间,left=mid+1;
2..mid落在不对应的区间,right=mid;,因为此时要寻找不对应的右区间
代码实现:
细节处理:
有可能·缺失的是最后一个数,此时的边界情况需要处理
4.前缀和
总结:
前缀和:就是为了快速的求出一段连续的区间的和
4.1.一维数组前缀和模板
解题思路:
第一步:预处理一个前缀和数组
dp[ i ] 表示:表示 [1,i ]区间的所有元素和;
dp[ i ]=dp[ i-1 ]+arr[ i ];
第二步:前缀和数组使用
[ L, R ] ---->dp[R]-dp[L-1] ;
代码实现:
细节处理:
为什么要对dp数组和arr数组初始化一个虚拟的节点?
为了处理边界情况:当我们默认arr【0】=0和dp【0】=0,arr数组和dp数组都是从下标为 1开始初始化,就不会出现 dp【-1】和arr【-1】的情况。
4.2.二维数组前缀和模板
解题思路:
1.预处理一个前缀和矩阵
dp[i]j] 表示:从[1,1]位置到 [i,j] 位置,这段区间里面所有元素的和
初始化方程:dp[i]j] =dp[i-1]j]+dp[i]j-1] +arr[i]j] -dp[i-1]j-1]
2.使用前缀和矩阵
给出了 两个坐标
结果:dp[x2][y2]-dp[x1-1][y2]-dp[x2][y1-1]+dp[x1-1][y1-1]
代码实现:
细节处理:
依旧是多开辟出,最上层的一行和最左边的一列
4.3.寻找数组的中心下标
解题思路:
根据题意可知,
假设我们此时的下边为3
左侧数之和 sum = nums[0] + nums[1] + nums[2] = 1 + 7 + 3 = 11 , 右侧数之和 sum = nums[4] + nums[5] = 5 + 6 = 11 ,
两者都没有加上下标为3的数组位置,
所以我们可以定义两个dp数组
一个是前缀和数组,一个是后缀和数组
前缀和状态转移方程:dp1[i]=dp[i-1]+arr[i];
后缀和状态转移方程:dp2[i]=dp2[i+1]+arr[i];
代码实现:
代码实现:
在前缀和中dp1[0]和dp2[n-1]默认为0,不进入循环
4.4.除自身以外数组的乘积
解题思路:
其实这道题的思路和上道题一摸一样
所以我们可以定义两个dp数组
一个是前缀积数组,一个是后缀积数组
前缀积状态转移方程:dp1[i]=dp[i-1]*arr[i];
后缀积状态转移方程:dp2[i]=dp2[i+1]*arr[i];
代码实现:
细节处理:
此时dp1[0]和dp2[0]都要初始化为1
4.5.和为K的子数组
解题思路:
这道题,可能大家都和我一样,首先想到的解法是滑动窗口,双指针的思想,但是这道题并不可以使用双指针,
注意:双指针的特性:left和right都是向前进行移动,进窗口就表示增加,出窗口就表示减少,但是这道题的范围是有0和负数的
这道题还是使用前缀和的思想,
第一步:定义一个指针i,使指针从前向后,进行遍历,找到一个位置,就计算他的前缀和,
sum:代表i位置的前缀和,
第二步:在i位置之前找到sum-i的前缀和,但是如果硬找的话,时间复杂的还是n的平方,
所以要借助一个hash表,来存储,前缀和和它对应的个数,
代码实现:
魔鬼细节处理:
1.前缀和加入哈希表的时机?
在计算i位置之前,哈希表里面只保存[0,i-1]位置的前缀和
2.不用真的创建一个前缀和数组
用一个变量 sum 来标记前一个位置的前缀和即可
3.如果整个前缀和等于k 呢?
提前在hash表中的 hash[ 0 ]=1;
4.6.和可被 K 整除的子数组
解题思路:
补充两个知识点:
1.同余定理:
a%p和b%p的余数是一样的
2.负数的修正
a%b:a如果是一个复数的话,结果是一个负数,
所以-->a%b+b:但是此时a如果是一个正数的话就不行
所以:(a%b+b)%b;
有了这里两个知识点以后,这道题的解题的思路就是上道题是一样的
代码实现:
细节处理:
3.如果整个前缀和等于k 的倍数呢?
提前在hash表中的 hash[ 0 ]=1;
4.7.连续的数组
解题思路:
依旧是因为此时的数组中有0的存在,所以不能使用滑动窗口的思想来做
根据题目的意思:数组中不是1就是0
我们此时可以将所有的0给的换成-1,
此时题目就转换成了:找出数组中最长的字串,此字串的和为0,就是 4.5了
代码实现:
细节处理:
此时需要的最后结果是长度
1.此时的hash表中,第一个存入的是前缀和,第二个存入的是前缀和的下标
2.如果整个前缀和等于k 呢?
提前在hash表中的 hash[ 0 ]=-1;
3.如果遇见的相同的前缀和要更新前缀和吗?
只更新结果,不加入到hash表中,因为越前的长度,一定比越后面的长度要长
4.8.矩阵区域和
解题思路:
给定一个范围,让我们求解这个矩形范围的数据和,第一时间想的的解法就是使用前缀和,但是这道题是二维前缀和
代码实现:
细节处理:
注意下标的映射关系,在原数组中和要提交的数组中,数据都是满的,但是dp数组中最上的和最左边的数据都是空的