目录
快慢指针
滑动窗口
76. 最小覆盖子串
567. 字符串的排列
438. 找到字符串中所有字母异位词
3. 无重复字符的最长子串
1248. 统计「优美子数组」
209. 长度最小的子数组
30. 串联所有单词的子串
239. 滑动窗口最大值
面试题57 - II. 和为s的连续正数序列
双指针
1. 两数之和
15. 三数之和
16. 最接近的三数之和
11. 盛最多水的容器
面试题 16.06. 最小差
240. 搜索二维矩阵 II
面试题21. 调整数组顺序使奇数位于偶数前面
1471. 数组中的 k 个最强值
之前在链表总结部分,提及到了快慢指针,凡是牵扯到链表中的精准定位问题,选择使用快慢指针,保准没错。
环的个数/起点,中间位置,两条链表找相交处,看了题目描述,直接快慢指针,手到擒来。
LeetCode 链表总结:https://blog.csdn.net/qq_41605114/article/details/105385252
关于链表中的快慢指针使用,此处不再多嘴,详情见上方链接
好文分享:
https://leetcode-cn.com/problems/find-the-duplicate-number/solution/qian-duan-ling-hun-hua-shi-tu-jie-kuai-man-zhi-z-3/
字符串或者数字之间求交集(在字符串中是子串),或者符合要求的连续子集或者子字符串,都可以使用滑动窗口进行解答。
滑动窗口的核心,就是在右侧区间不断扩大的同时,根据完成解的要求,右移左侧指针,依次找到最优解。
@powcai对滑动窗口的题目进行了汇总:
https://leetcode-cn.com/problems/longest-substring-without-repeating-characters/solution/hua-dong-chuang-kou-by-powcai/
@labuladong关于滑动窗口,总结了一套模板:https://leetcode-cn.com/problems/minimum-window-substring/solution/hua-dong-chuang-kou-suan-fa-tong-yong-si-xiang-by-/%E5%8F%8C%E6%8C%87%E9%92%88%E6%8A%80%E5%B7%A7.md
模板如下:
/* 滑动窗口算法框架 */
void slidingWindow(string s, string t) {
//(1)
unordered_map need, window;
for (char c : t) need[c]++;
//(2)
int left = 0, right = 0;
int valid = 0;
while (right < s.size()) {
// c 是将移入窗口的字符
char c = s[right];
// 右移窗口
right++;
// 进行窗口内数据的一系列更新
...
/*** debug 输出的位置 ***/
printf("window: [%d, %d)\n", left, right);
/********************/
// 判断左侧窗口是否要收缩
while (window needs shrink) {
// d 是将移出窗口的字符
char d = s[left];
// 左移窗口
left++;
// 进行窗口内数据的一系列更新
...
}
}
}
作者:labuladong
链接:https://leetcode-cn.com/problems/minimum-window-substring/solution/hua-dong-chuang-kou-suan-fa-tong-yong-si-xiang-by-/
来源:力扣(LeetCode)
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
以下内容也主要是参考上面提到的多篇文章并加入自己的理解。
模板介绍:
(1)中,初始化两个Hash表,window代表我们的滑动窗口,needs代表我们需要找的目标值及个数
(2)中,初始化滑动窗口的左右指针,整个过程中滑动窗口都是一个左闭右开的区间,即[left,right),其中valid表示窗口中满足needs条件的字符个数。
此模板是左闭右开,务必注意right和left更新的位置,更新的位置不一样,那么我们得到的解也就不一样
滑动窗口的核心,就是先找到一个满足要求的解,然后不断的收缩空间,尝试得到最优解。
right不断右移,扩大窗口范围,在扩大的过程中,不断判断,是否得到了一个满足要求的解,一旦得到一个解,在更新解的同时
开始进行窗口left的右移,我们开始缩小窗口,直到在窗口中失去解,完成lef的移动,right继续右移
我们也可以这么认为:
不断找到解,不断失去解,再次找到解。
首先找到一个解,然后移动left指针,直到区间失去了解,失去解是为了在剩下的内容中找到另一组解
失去解后,right不断右移,直到我们再次找到解。以此循环,知道遍历所有要查找内容。
如果说二分查找的精华在区间中实在有解,滑动窗口的精髓在于舍弃解后,再次寻找解。
下面找一道题目作为依托,进一步阐述滑动窗口的原理
https://leetcode-cn.com/problems/minimum-window-substring/
class Solution {
public:
string minWindow(string s, string t) {
unordered_map window,need;
for(auto item:t) need[item]++;//目标元素出现次数
int left = 0,right = 0,valid = 0;
int length = INT_MAX,begin = 0;
int size = need.size();
while(right < s.size())
{
char Rtemp = s[right];//当前字符
right++;
if(need[Rtemp])//一旦当前字符是目标值之一,那么我们进行记录,目前窗口包含目标值
{
window[Rtemp]++;//更新窗口包含目标值的情况
if(window[Rtemp] == need[Rtemp])
//一旦某个目标值元素在窗口中的个数满足要求,那么更新valid
valid++;
}
while( valid == size )//一旦条件成立,说明窗口中目前已经包含了解
{
//更新解
if(right - left
下面我们来图解一下整个过程:
right不断地右移,知道指向C,此时valid == 3,right照常自增1。第一次找到解,此时更新解,len = 6,begin = 0;
之后left右移,从解中删除A,left自动增1,valid == 2,区间内不包含解,那么直接break,right继续右移
知道right找到最后的A,right自增1,valid == 3,找到第二个解,但是不如第一个解短,不更新解,left开始右移
直到将C移除区间,right继续右移
当right在等于s.size()的时候,选择C,然后自增1,此时valid等于3,left开始右移
当left选中E的时候,left自增,到达了B的位置,此时进入新的一轮循环,更新解,排除B后,valid也不在等于3,跳出循环
此时right也不符合条件,跳出循环。
最优解已经被记录,完成。
最重要的细节部分,是对区间和最优解的更新位置,务必注意,因为左闭右开,right在更新解前自增,left在更新解后自增
https://leetcode-cn.com/problems/permutation-in-string/
class Solution {
public:
bool checkInclusion(string s1, string s2) {
unordered_map window,need;
for(auto item : s1) need[item]++;
int left = 0, right = 0;
int len = INT_MAX;
int valid = 0;
int nsize = need.size(); //避免重复元素
while(right
架构完全一样,改都没有改,本题只需要增加一些判断即可,需要是子串,而且要求无视排列。
那么只要在s2中找到一个子串,均包含s1,且长度和s1相等,那一定就是找到了,否则就是没有找到。
https://leetcode-cn.com/problems/find-all-anagrams-in-a-string/
class Solution {
public:
vector findAnagrams(string s, string p) {
unordered_map window,need;
for(auto item:p) need[item]++;
int right = 0,left = 0;
int valid = 0,len = p.size();
int nsize = need.size();//避免重复元素
vector Res;
while(right
此题和上一道题非常相似,都是子串,那么我们还是从长度入手进行解题。
但是有时我们需要的题目不一定如此复杂,下面的第3题和209题,就是滑动窗口的另外两种风格。
但是核心不变,就像二分查找的区间,不管左右区间如何迭代,解都是要在区间内的
滑动窗口也是如此,解一定要保持在窗口内,上面几道字符串类型的题目,都体现了滑动窗口的核心:
不断找到解,不断失去解,再次找到解。
首先找到一个解,然后移动left指针,直到区间失去了解,失去解是为了在剩下的内容中找到另一组解
失去解后,right不断右移,直到我们再次找到解。以此循环,知道遍历所有要查找内容。
如果说二分查找的精华在区间中实在有解,滑动窗口的精髓在于舍弃解后,再次寻找解。
下面的题目更是体现了这点。
https://leetcode-cn.com/problems/longest-substring-without-repeating-characters/
还是左右指针,滑动窗口的套路,
class Solution {
public:
int lengthOfLongestSubstring(string s) {
unordered_mapwindow;
int right = 0,left = 0;
int len = 0;
while(right1)//清空部分
{
char ltemp = s[left];
left++;
window[ltemp]--;
}
len = max(len,right - left);
}
return len;
}
};
window[temp]大于1的时候,说明有重复的了,那么我们把重复的排除出去即可,不断移动left指针,直到区间内再次恢复为只含
有单独元素的情况
滑动窗口的变形:
https://leetcode-cn.com/problems/count-number-of-nice-subarrays/
本题在某种意义上,甚至是有些违背滑动窗口的一般结题思路的
滑动窗口一般是找到解,然后将解抛出,继续寻找最优解。
此题我们在找到解的时候,需要特别注意,如果理解抛出解,会少算很多情况
比如示例3
以第一个2开头,就有四种情况,以第二个2开头也有四种,总共16种,所以我们的滑动窗口需要进行一下修改
本题我们需要在找到一组解后,计算这个解中两端奇数前后的偶数个数,算出排列组合的情况
class Solution {
public:
int numberOfSubarrays(vector& nums, int k) {
//滑动窗口
int size = nums.size();
if(size target;
int leftnumber = 0,rightnumber = 0;
while(right
使用两个int变量,leftnumber 和 rightnumber,分别记录第一个奇数距离left的距离,和最后一个奇数距离right的距离
找到排列组合之后,我们要进行滑动窗口的老操作了,就是边界收缩,让left不断收缩,直到没有解
这是一道非常非常有趣的滑动窗口题目。反常规。
https://leetcode-cn.com/problems/minimum-size-subarray-sum/
class Solution {
public:
int minSubArrayLen(int s, vector& nums) {
int right = 0,left = 0;
int sum = 0,len = INT_MAX;
while(right=s)
{
len = min(len,right - left);
sum -= nums[left];
left++;
}
}
return len == INT_MAX?0:len;
}
};
当和到达要求的时候,此时可以求长度了,我们得到了解,下面就要舍弃解,不断移动left,直到失去解
然后我们才能去找更优的解。
https://leetcode-cn.com/problems/substring-with-concatenation-of-all-words/
参考内容:
https://leetcode-cn.com/problems/substring-with-concatenation-of-all-words/solution/30-by-ikaruga/
本题看似非常简单,不就是把找元素换找单词吗?步长从1改成单词长度,不就分分钟的事情吗?
但是此题的情况远比这复杂,因为题目可没有说,其他干扰项的长度也是步长
比如:
"lingmindraboofooowingdingbarrwingmonkeypoundcake"
["fooo","barr","wing","ding","wing"]
"aaaaaa"
["aaa","aaa"]
看了简直要死,第一个例子,第一个大的干扰项长度是13,但是平常的步长是4
那么本题该如何应对,我们在滑动窗口外侧加循环,让整个滑动窗口的起点,从0开,一直到步长4为止,起点分别是
0,1,2,3,按照一个步长的循环来进行,这样总能规避掉各种长度的干扰项目。
题目要求找到的解,必须长度和words所有元素的长度加起来一致,但是排列无所谓,所以求子串是否是我们想要的解,判断条件就是长度相等
本题细节非常多,可谓是滑动窗口之最了
先看核心代码
int left = i;
int right = i,valid = 0;
unordered_map window;
while(right
①处,因为题目要求,不能含有其他多余字符,所以整个长度必须是匹配的
②处,这个地方写快了很容易出错,一旦少一个字符,我们就要将valid减去1
因为字典中每个字符都要出现,我们还是用Hash表进行记录,但是这样有时候会记录重复的内容
所以我们在加完temp后,立即比较,如果数量够了就将valid加1,之后有重复的也不管了
class Solution {
public:
vector findSubstring(string s, vector& words) {
int s_size = s.size(),w_size = words.size();
if(s_size == 0||w_size == 0) return {};
unordered_map need;
for(auto item:words) need[item]++;//统计每个类型字符串出现的频率
int validsize = need.size();
int step = words[0].size();
int length = w_size*step;//为了后面去除错误答案打下基础
vector Res;
for(int i = 0;i window;
while(right
最后一题,借滑动窗口之名,而无滑动窗口之实。
https://leetcode-cn.com/problems/sliding-window-maximum/
面试题59 - I. 滑动窗口的最大值
https://leetcode-cn.com/problems/hua-dong-chuang-kou-de-zui-da-zhi-lcof/
要求线性时间:我们可以这么做,维护一个栈,每次都选择滑动窗口中的最大值压入,之后每次窗口移动一次,只把串口right处的值和栈顶元素比较即可,线性时间复杂度,需要注意的是这个最值,一定要在滑动窗口的范围内:我们尝试实现一下代码:
我们先处理第一个滑动窗口:
//先处理第一个滑动窗口
int MAXnum = 0,index = 0;
for(int i = 0;i
比较了k次,选出最值,那么我们下面从第二个滑动窗口开始:
int left = 1,right = k;//我们从第二个滑动窗口开始
while(rightright)
{
MAXIndex.pop();
MAXnum = nums[left],index = left;
//只比较left到right-1这个区间内的值,下面还有一个if比较right的值
for(int i = left + 1;iMAXnum) {MAXnum = nums[i],index = i;}
}
MAXIndex.push(index);
}
//一般情况下:只比较栈中的最值和right指针的位置
if(nums[right]>nums[MAXIndex.top()]) {MAXIndex.pop();MAXIndex.push(right);}
}
else MAXIndex.push(right);
//选择最值并添加到结果中,整个滑动窗口右移
cout<
完成代码如下:
class Solution {
public:
vector maxSlidingWindow(vector& nums, int k) {
if(nums.empty()) return {};
vectorRes;
stackMAXIndex;
int size = nums.size();
//先处理第一个滑动窗口
int MAXnum = 0,index = 0;
for(int i = 0;iright)
{
MAXIndex.pop();
MAXnum = nums[left],index = left;
for(int i = left + 1;iMAXnum) {MAXnum = nums[i],index = i;}
}
MAXIndex.push(index);
}
if(nums[right]>nums[MAXIndex.top()]) {MAXIndex.pop();MAXIndex.push(right);}
}
else
MAXIndex.push(right);
cout<
程序一定要注意,最值要在范围内:
下面是官方解法:
(官方解法:https://leetcode-cn.com/problems/sliding-window-maximum/solution/hua-dong-chuang-kou-zui-da-zhi-by-leetcode-3/)
(C++版本:https://leetcode-cn.com/problems/sliding-window-maximum/solution/dan-diao-dui-lie-by-labuladong/)
本题总的来说,不算是滑动窗口,只是名字一样,实际方法是双项队列deque
为了降低时间复杂度,我们观察一下窗口移动的过程类似于队列出队入队的过程,每次队尾出一个元素,然后队头插入一个元素,求该队列中的最大值
每次的值都和队尾元素比较,将小的弹出,大的暂时放入,队首一直都是最大值(在滑动窗口范围内)
class Solution {
public:
vector maxSlidingWindow(vector& nums, int k) {
if(nums.empty()) return {};
vectorRes;
dequeMAXindex;
//处理第一个窗口
for(int i = 0;iright||MAXindex.front()
以上情况,正好是线性的时间复杂度
https://leetcode-cn.com/problems/he-wei-sde-lian-xu-zheng-shu-xu-lie-lcof/
算法参考:https://leetcode-cn.com/problems/he-wei-sde-lian-xu-zheng-shu-xu-lie-lcof/solution/shi-yao-shi-hua-dong-chuang-kou-yi-ji-ru-he-yong-h/
从头开始滑动窗口,找到解后,因为以这个为开头的解只可能有一个,所以左侧窗口边界收缩
这个方法非常巧妙,但是要注意细节,注意循环的break条件,否则会找不全内容
class Solution {
public:
vector> findContinuousSequence(int target) {
int Uplimit = target/2;
//滑动窗口
vector> Res;
int left = 1,right = 1;
int Sum = 0;
while(left<=Uplimit)//小细节
{
if(Sum>target)
{
Sum -= left;
left++;
}
else if(SumTemp;
for(int i = left;i
有下面一道题目,有一个排序数组,现在需要在线性的时间复杂度下,找出目标和,我们应该怎么做,Hash表是很好的方法
https://leetcode-cn.com/problems/two-sum/
leetcode天字一号题目,两数之和,又回到了梦开始的地方
Hash表:
class Solution {
public:
vector twoSum(vector& nums, int target) {
unordered_map m;
for(int i = 0;i
但是稍稍改变题目,返回的是数组的元素而不是下标,双指针法也毫不逊色:
class Solution {
public:
vector twoSum(vector& nums, int target) {
int size = nums.size();
sort(nums.begin(),nums.end());//排序
vector Res;
int left = 0,right = size-1;
while(left0&&nums[right] == nums[right-1])right--;
left++,right--;
}
else if((nums[left]+nums[right])>target) right--;
else left++;
}
return Res;
}
};
以上是一个典型的双指针技巧。
从排序数组的两端,不断向中间逼近,下面我们看图解:
如果说快慢指针是数学问题,滑动窗口的一个非常傲娇的过程,先找到解,再将解移除区间,然后再次寻找解
双指针的核心,就是根据数列的性质(从小到大排序),让两个指针移动。
这些性质也就省去了很多冗余不必要的计算。
https://leetcode-cn.com/problems/3sum/solution/three-sum-ti-jie-by-wonderful611/
双指针一般出现在数组问题中,数组问题是个非常庞大的系列,其中使用到的方法也是各式各样。
https://leetcode-cn.com/problems/3sum/
初见此题,在没有任何准备的情况下,暴力解法就是一种解法,在暴力解法的基础上,Hash表也是一个优化的选择。
但是面对数组问题,可以尝试排序后使用双指针的方法进行解决。
下面收录了一些非常好的解法和解题思路讲解:
https://leetcode-cn.com/problems/3sum/solution/san-shu-zhi-he-cshi-xian-shuang-zhi-zhen-fa-tu-shi/
https://leetcode-cn.com/problems/3sum/solution/man-hua-jue-bu-wu-ren-zi-di-xiang-kuai-su-kan-dong/
https://leetcode-cn.com/problems/3sum/solution/three-sum-ti-jie-by-wonderful611/
以上三篇文章,都是从不同的角度对本问题提出了分析的方法
变化太多的情况,逻辑上我们就要把他变成变化少的情况,固定一个数的位置,去找剩下两个数字。
class Solution {
public:
vector> threeSum(vector& nums) {
vector> Res;
int size = nums.size();
if(size <= 2) return Res;
sort(nums.begin(),nums.end());
int pre = 0,right = size-1,left = pre+1;
while(pre<=size-3)
{
int sum = 0;
while(left Temp;
sum = nums[pre]+nums[left]+nums[right];
if(sum == 0)
{
Temp.push_back(nums[pre]);
Temp.push_back(nums[left]);
Temp.push_back(nums[right]);
left++,right--;
}
else if(sum>0) right--;
else left++;
if(!Temp.empty()) Res.push_back(Temp);
}
pre++,right = size-1,left = pre+1;
}
return Res;
}
};
会发现,计算重复了,那么,我们需要剔除重复的结果
如果pre重复了,那么我们继续加,直到遇到一个新的pre
对于right和left也是一样的,既然重复,那么就不断循环,直到找到一个区别于刚才一组解的值
代码如下:
class Solution {
public:
vector> threeSum(vector& nums) {
vector> Res;
int size = nums.size();
if(size <= 2) return Res;
sort(nums.begin(),nums.end());
int pre = 0,right = size-1,left = pre+1;
while(nums[0]<=0&&pre<=size-3)
{
int sum = 0;
while(left Temp;
sum = nums[pre]+nums[left]+nums[right];
if(sum == 0)
{
Temp.push_back(nums[pre]);
Temp.push_back(nums[left]);
Temp.push_back(nums[right]);
//位置一定要放对了,一定要是在等于0之后,剔除重复的内容
while(right>0&&nums[right] == nums[right-1]) right--;
while(left0) right--;
else left++;
if(!Temp.empty()) Res.push_back(Temp);
}
while(pre
版本二:
class Solution {
public:
vector> threeSum(vector& nums) {
if(nums.empty()) return {};
int size = nums.size();
vector>Res;
sort(nums.begin(),nums.end());//排序
int PCur = 0,left = 1,right = size-1;
while(PCur<=size-2)//倒数第二个
{
if(PCur-1>=0&&nums[PCur] == nums[PCur-1]) {PCur++;continue;}
//和自己的前一个比较,如果一样,那么我们就要跳过这个值
left = PCur+1,right = size-1;
while(left0)right--;
else if(temp == 0)
{
vector Vtemp;
Vtemp.push_back(nums[PCur]);
Vtemp.push_back(nums[left]);
Vtemp.push_back(nums[right]);
Res.push_back(Vtemp);
right--;left++;
//放置于正确的位置,当等于零时,移动左右指针,知道遇到不一样的内容为止,放置重复
while(right>=0&&nums[right] == nums[right+1]) right--;
while(left<=size-1&&nums[left] == nums[left-1]) left++;
}
}
PCur++;
}
return Res;
}
};
下面的解中,包含了各种个数的求和总结:
https://leetcode-cn.com/problems/3sum/solution/man-hua-jue-bu-wu-ren-zi-di-xiang-kuai-su-kan-dong/
那么将上面的题目稍作改变:
https://leetcode-cn.com/problems/3sum-closest/solution/
类比上一道题目。本题可以选择重复内容,只要求接近,那么我们不断去更新最小值,再根据目前三个数的和与target的关系,移动指针,不断去逼近正确答案。
class Solution {
public:
int threeSumClosest(vector& nums, int target) {
int size = nums.size();
sort(nums.begin(),nums.end());
if(size < 3) return 0;
int Min = INT_MAX,pre = 0,right = size-1,left = pre+1,Res = 0;
while(predelta)
{
Min = delta;
Res = sum;
}
if(sum
上面的题目是一个类型,那么当我们需要不能排序的情况,或者整体大小情况未知的时候,该怎么办?
https://leetcode-cn.com/problems/palindrome-number/
灵活机动,将数字变成字符串,这样就可以诸位进行访问,然后双指针操作,一个从左一个从右开始,向中间收缩,一旦不相等,break;
class Solution {
public:
bool isPalindrome(int x) {
string num = to_string(x);
int size = num.size();
int right = size-1,left = 0;
while(left
https://leetcode-cn.com/problems/container-with-most-water/
(参考解答:https://leetcode-cn.com/problems/container-with-most-water/solution/on-shuang-zhi-zhen-jie-fa-li-jie-zheng-que-xing-tu/)
暴力解法:
class Solution {
public:
int maxArea(vector& height) {
int size = height.size();
if(size == 0) return 0;
if(size == 2)
{
return min(height[0],height[1]);
}
int pre = 0,right = size-1,left = pre+1,MAX = 0;
while(pre
显然是超时,那么我们应该怎样解决这个问题呢?简化双指针
就两个指针,一左一右,计算面积,然后收缩,怎么收缩呢,收缩牵扯到长方形长边的缩短,所以要找出最大值,我们要固定right和left中的较大值,移动较小的值。
class Solution {
public:
int maxArea(vector& height) {
int left = 0, right = height.size() - 1;
int Res = 0;
while (left < right) {
int sum = min(height[left], height[right]) * (right - left);
Res = max(Res, sum);
//移动小的边界
if (height[left] <= height[right]) ++left;
else right--;
}
return Res;
}
};
// 著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
(官方题解:https://leetcode-cn.com/problems/container-with-most-water/solution/sheng-zui-duo-shui-de-rong-qi-by-leetcode-solution/)
https://leetcode-cn.com/problems/smallest-difference-lcci/solution/
此题和上面的题目如出一辙,上面是没有办法排序,本次是没有办法知道两个数组彼此之间的大小情况
那么还是老办法,让他们自己排序,然后两个指针,各自指向各自的首地址,算完差值后,二者比较,大的肯定是动不了,大的动了二者差距越来越大,小的动,以此类推不断循环。
class Solution {
public:
int smallestDifference(vector& a, vector& b) {
int sizea = a.size(),sizeb = b.size();
if(sizea == 0||sizeb == 0) return 0;
sort(a.begin(),a.end());
sort(b.begin(),b.end());
long p1 = 0,p2 = 0,res = INT_MAX;
while(p1
https://leetcode-cn.com/problems/search-a-2d-matrix-ii/
本题最大的技巧,是从右上角开始,因为如果从左上角开始,上面右面都是大值,这怎么移动?
反而是右上角a点为起始,比target大了,左移(因为a的值本行最大),比target小了,下移(因为a的值本行最小)
class Solution {
public:
bool searchMatrix(vector>& matrix, int target) {
if(matrix.empty()||matrix[0].empty()) return false;
int m = matrix.size(),n = matrix[0].size();
;
int Hbegin = 0,Lbegin = n-1;
while(Hbegin=0)
{
if(matrix[Hbegin][Lbegin] == target) return true;
if(matrix[Hbegin][Lbegin] > target) Lbegin--;
else Hbegin++;
}
return false;
}
};
https://leetcode-cn.com/problems/diao-zheng-shu-zu-shun-xu-shi-qi-shu-wei-yu-ou-shu-qian-mian-lcof/
优秀代码:
class Solution {
public:
vector exchange(vector& nums) {
if(nums.empty()) return {};
int size = nums.size();
int left = 0,right = size - 1;
while(left=0&&nums[right]%2==0) right--;//找最后一个奇数
cout<<"right"<=size||left>right) break;
swap(nums[left],nums[right]);
left++,right--;
}
return nums;
}
};
垃圾(但是实在是太容易想到了):
class Solution {
public:
vector exchange(vector& nums) {
if(nums.empty()) return {};
int size = nums.size();
vectorRes;
unordered_mapM;
for(int i = 0;i
https://leetcode-cn.com/problems/the-k-strongest-values-in-an-array/
周赛第二题:
本题叙述还是比较复杂的,我们整理一下内容:
前两点非常好满足
第三点:我们看表达式一的样子,就是在求和中位数的差值(绝对值),越大越好,那么对于一个排序数组来说,首尾必然是差值最大的地方,但是具体差多少,是首差值大还是尾差值大,我们不知道
再看表达式二,当二者差值相等,本身值谁大选择谁,因为是排序数组,显然是序号越大,越靠近尾部的值大。
综上,直接双指针法,left = 0,right = size - 1
对比 abs(arr[right] - arr[mid]) 和 abs(arr[mid] - arr[left])的关系,大于等于,其实都是选择right值
只有当小于的时候,才会选择left,我们让while跳出条件为left = right即可。
class Solution {
public:
vector getStrongest(vector& arr, int k) {
if(arr.empty()) return {};
sort(arr.begin(),arr.end());//排序
//找中位数
int size = arr.size();
int mid = arr[size/2];
//找到合适的内容
vectorRes;
int right = size-1,left = 0;
mid = left + (right - left)/2;
while(left<=right)
{
if(k == 0) break;
if(abs(arr[right] - arr[mid]) > abs(arr[mid] - arr[left])) //第一条规则
{Res.push_back(arr[right]);right--;k--;}
else if(abs(arr[right] - arr[mid]) == abs(arr[mid] - arr[left]))//第二条规则
{Res.push_back(arr[right]);right--;k--;}//必然是后面的大
else {Res.push_back(arr[left]);left++;k--;}
}
return Res;
}
};
https://leetcode-cn.com/problems/shortest-unsorted-continuous-subarray/
本题我们要的是双指针,从头开始,扫描,找单调递增中的异常,也就是下降沿
找到下降沿后,需要移动左指针,找到下降沿的起点,同时移动右指针,找到下降沿的终点
class Solution {
public:
int findUnsortedSubarray(vector& nums) {
if(nums.empty()) return 0;
int size = nums.size();
int right = 1,left = 0,begin = 0;
int NewB = size,NewE = 0;
while(rightnums[right])
{
int Ltemp = left;
while(Ltemp>=0)//检查左侧,有没有高的
{
if(nums[Ltemp]>nums[right]) Ltemp--;
else break;
}
int Rtemp = right;
while(Rtempnums[Rtemp]) Rtemp++;
else break;
}
NewB = min(NewB,Ltemp);NewE = max(NewE,Rtemp);//记录起点和终点
right++;left++;
}
}
return NewE==0?0:NewE-NewB-1;
}
};
https://leetcode-cn.com/problems/move-zeroes/
双指针原地交换,并不难
class Solution {
public:
void moveZeroes(vector& nums) {
if(nums.empty()) return;
int right = 1,left = 0;
while(right1) left++;
else {right++;left++;}
}
else {right++;left++;}
}
return;
}
};
https://leetcode-cn.com/problems/sort-colors/
三指针分类解题:
三个指针,C2探索2的左边界,C0探索0的右边界,PCur保存正常遍历
当PCur指向0时,将C0和PCur交换,二者同时增加步长
当PCur指向2时,将C2和PCur交换,此时收缩2的边界指针C2,也就是将C2--,但是此时PCur不能动,因为你不知道现在PCur指向的是谁,需要在下一轮循环中判断。
比如下面的情况
错误程序:
class Solution {
public:
void sortColors(vector& nums) {
//原地排序
int C2 = nums.size()-1,C0 = 0,PCur = 0;
while(PCur
本题要特别的注意细节问题:
class Solution {
public:
void sortColors(vector& nums) {
//原地排序
int C2 = nums.size()-1,C0 = 0,PCur = 0;
while(PCur<=C2)
{
if(nums[PCur]==0)//等于0
{
swap(nums[PCur],nums[C0]);
C0++,PCur++;//交换完后,都移动
}
else if(nums[PCur]==2)//等于2
{
swap(nums[PCur],nums[C2]);
C2--;//只减少2的边界
}
else PCur++;
}
}
};