leetcode学习记录_双指针

42. 接雨水

示例 :
在这里插入图片描述

输入:height = [0,1,0,2,1,0,1,3,2,1,2,1]
输出:6
解释:上面是由数组 [0,1,0,2,1,0,1,3,2,1,2,1] 表示的高度图,在这种情况下,可以接 6 个单位的雨水(蓝色部分表示雨水)

来源:力扣(LeetCode)

思路:
蓄水必须得有个条件:左右都比当前位置高,才能蓄水,所以单从一个方向遍历的话,容易考虑不周到,所以从两边往中间逼近

双指针,一个从左开始,一个从右开始,同时用max_L 和 max_R来维护当前左/右的最大高度,
首先从左开始
如果当前高度高于max_L就说明当前位置不能蓄水,你是当前最高的,还怎么蓄水呢,直接更新max_L就好
如果当前高度小于最大高度,就说明此处能蓄水(max_L - 当前高度)

当然你可能会问,如果后面的高度全部都比这个max_L小,那(max_L - 当前高度)不就错了吗?确实如此,所以我们才用双指针左右逼近的方法,只有当max_L < max_R时,才用这个公式,并且L++;

当max_L < max_R 时就反过来,具体见代码

class Solution {
public:
    int trap(vector<int>& height) {
        int L = 0 , R = height.size()-1 ,max_L = 0 , max_R = 0 , res = 0;
        while(L < R)
        {
           if(height[L] < height[R])
           {//如果当前高度不是最高的,就更新最高高度
           		if(height[L] >= max_L)
           		{
           			max_L = height[L];
           		}
//既然不是最高,就说明左边有比你高的,那就可以蓄水,就更新res           		
           		else
           		{
           			res += max_L - height[L];
           		}
           		L++;//
           }
           else
           {
           		if(height[R] >= max_R)
           		{
           			max_R = height[R];
           		}
           		else
           		{
           			res += max_R - height[R];
           		}
           		R--;
           }
        }
        return res;
    }
};


11. 盛最多水的容器

给你 n 个非负整数 a1,a2,…,an,每个数代表坐标中的一个点 (i, ai) 。在坐标内画 n 条垂直线,垂直线 i 的两个端点分别为 (i, ai) 和 (i, 0) 。找出其中的两条线,使得它们与 x 轴共同构成的容器可以容纳最多的水。

说明:你不能倾斜容器。

示例 1:在这里插入图片描述
输入:[1,8,6,2,5,4,8,3,7]
输出:49
解释:图中垂直线代表输入数组 [1,8,6,2,5,4,8,3,7]。在此情况下,容器能够容纳水(表示为蓝色部分)的最大值为 49。

来源:力扣(LeetCode)

思路和上一题的接雨水有一点像,也是双指针从左右逼近,当两指针相遇即搜索结束,那怎么缩小区间呢?题目要求最大面积,面积=宽*高嘛,一开始当L和R处于两端的时候,宽最大,所以接下来如果想找更大的面积,那L和R这两条边里,最短的那个必须得有增加(因为面积取决于短的那个),我才敢放心得减少宽啊,不然面积不就越来越小了吗,所以当所以当height[L] < height[R] 时,L++;
反过来就R–,这里可以用else,因为还有个两种相等的情况,也包括在else的范围内了

class Solution {
public:
    int maxArea(vector<int>& height) {
		int L = 0, R = height.size()-1, res = 0;
		while(L < R)
		{
			int area = (R - L) * min(height[L], height[R])
			res = max(res, area);			
			if(height[L] < height[R]) L++;
			else R--;
		}
		return res;
    }
};


56. 合并区间

以数组 intervals 表示若干个区间的集合,其中单个区间为 intervals[i] = [starti, endi] 。请你合并所有重叠的区间,并返回一个不重叠的区间数组,该数组需恰好覆盖输入中的所有区间。
示例 1:

输入:intervals = [[1,3],[2,6],[8,10],[15,18]]
输出:[[1,6],[8,10],[15,18]]
解释:区间 [1,3] 和 [2,6] 重叠, 将它们合并为 [1,6].

来源:力扣(LeetCode)
思路,先把数组 intervals 排序,这样如果有重叠的区间,那肯定是挨在一起的,然后不重叠的话,就把当前数据存入结果
重叠的话,只有两种情况
在这里插入图片描述
可以发现,当L < r时就代表重叠,重叠了就要合并,l自然不变,因为时排过序的,r就 = max ( r , R )选大的那个

class Solution {
public:
    vector<vector<int>> merge(vector<vector<int>>& intervals) {
        int N = intervals.size();
        if(N == 0) return {};
        vector<vector<int>>res;
        sort(intervals.begin(),intervals.end());//排序
        res.push_back({intervals[0][0],intervals[0][1]});//给res初始化
        for(int i = 0;i<N;++i)//遍历intervals
        {
        	// l = res.back()[0]
        	// r = res.back()[1];
            int L = intervals[i][0], R = intervals[i][1];
            if(res.back()[1] < L)
            {
                res.push_back({L , R});//不重叠就把这个区间填入
            }
            else
            {//重叠就改变结果数组里的r,选r和R之间的最大值
                res.back()[1] = max(res.back()[1], R);
            }
        }
        return res;
    }
};


三数之和:

给你一个包含 n 个整数的数组 nums,判断 nums 中是否存在三个元素 a,b,c ,使得 a + b + c = 0 ?请你找出所有和为 0 且不重复的三元组。

注意:答案中不可以包含重复的三元组。
示例 1:

输入:nums = [-1,0,1,2,-1,-4]
输出:[[-1,-1,2],[-1,0,1]]
来源:力扣(LeetCode)

要写出这题,首先要会两数之和,不是返回下标的,是返回值的那种,因为我们要用排序+ 双指针,如果要返回下标,排序过后下标就乱了
先说说两个数的排序双指针:

在这里插入图片描述
排序是为了更好的用双指针去定位,有一点二分法的感觉,但性质完全不一样
然后回到三数之和,,其实很简单
我们遍历数组的每个数字nums[ i ] , 然后调用twosum的程序,修改一下原towsum程序的参数,返回twosum的二维数组,在二维数组的每一维里面都插入nums[ i ],就得到了想要三元数组

最麻烦的地方还是去重,但其实都是一个套路
代码:

class Solution {
public:
    vector<vector<int>> twoSum(vector<int>& nums, int start, int target)
    {//这里L得从start开始
        int L = start, R = nums.size()-1;
        vector<vector<int>> res;
        while(L < R)
        {
            int temp = nums[L] + nums[R];
            if(temp == target)
            {//满足条件,就填入res数组
                res.emplace_back(vector<int>{nums[L],nums[R]});
                while(L < R && nums[L] == nums[L+1])  L++;//去重
                L++;
                while(L < R && nums[R] == nums[R-1]) R--;//去重
                R--;
            }
            else if (temp < target)
            {
                while(L < R && nums[L] == nums[L+1])  L++;//去重
                L++;
            }
            else
            {
                while(L < R && nums[R] == nums[R-1]) R--;//去重
                R--;
            }
        }
        return res;
    }
    vector<vector<int>> threeSum(vector<int>& nums) {
        int N = nums.size();
        if(N < 3) return {};//特殊情况
        vector<vector<int>> res;
        sort(nums.begin(),nums.end());//排序
        for(int i = 0;i<N;++i)//遍历
        {
            vector<vector<int>>temp = twoSum(nums,i+1,0-nums[i]);
            for(vector<int>& buf : temp)
            {//给得到的二维数组填入nums[i]
                buf.emplace_back(nums[i]);
                res.emplace_back(buf);
            }//这里也要去重
            while (i < N - 1 && nums[i] == nums[i + 1]) i++;
        }
        return res;
    }
};

不考虑复用性的话就不必写成两个函数,
下面是精简版:

class Solution {
public:
    vector<vector<int>> threeSum(vector<int>& nums) {
        const int N = nums.size();
        vector<vector<int>>res;
        sort(nums.begin(),nums.end());
        for(int i = 0;i<N;++i)
        {
            int L = i+1, R = N-1;
            while(L < R)
            {
                int temp = nums[L]+nums[R]+nums[i];
                if(temp == 0)
                {
                    res.emplace_back(vector<int>{nums[L],nums[R],nums[i]});
                    while(L  < R && nums[L] == nums[L+1]) L++;L++;
                    while(L  < R && nums[R] == nums[R-1]) R--;R--;
                }
                else if(temp < 0)
                {
                    while(L  < R && nums[L] == nums[L+1]) L++;L++; 
                }
                else
                {
                    while(L  < R && nums[R] == nums[R-1]) R--;R--;
                }
            }
            while (i < N - 1 && nums[i] == nums[i + 1]) i++;
        }
        return res;
    }
};


16. 最接近的三数之和

给定一个包括 n 个整数的数组 nums 和 一个目标值 target。找出 nums 中的三个整数,使得它们的和与 target 最接近。返回这三个数的和。假定每组输入只存在唯一答案。
示例:
输入:nums = [-1,2,1,-4], target = 1
输出:2
解释:与 target 最接近的和是 2 (-1 + 2 + 1 = 2) 。
来源:力扣(LeetCode)
这一题和上面的三数之和特别像,核心思路都一样,三数之和要返回组合,这个直接返回值就行,因为是最接近的值,所以得用绝对值判断是否要更新结果,不像上一题可以直接填入结果数组
代码如下:

(这题可以不去重,反正最佳答案也只有一个,但是去重可以提高一点效率)

class Solution {
public:
    int threeSumClosest(vector<int>& nums, int target) {
        const int N = nums.size();
        sort(nums.begin(),nums.end());
        int res = nums[0]+nums[1]+nums[2];
        for(int i = 0;i<N;++i)
        {
            int L = i+1, R = N -1;
            while(L < R)
            {
                int temp = nums[L]+nums[R]+nums[i];
//根据绝对值来决定是否更新答案;                
                if(abs(target-res) > abs(target-temp)) res = temp;
                if(temp == target) return target;
                else if(temp < target) L++;
                else if(temp > target)R--;
            }
        }
        return res;
    }
};


283. 移动零

给定一个数组 nums,编写一个函数将所有 0 移动到数组的末尾,同时保持非零元素的相对顺序。
示例:
输入: [0,1,0,3,12]
输出: [1,3,12,0,0]
说明:
必须在原数组上操作,不能拷贝额外的数组。
尽量减少操作次数。
来源:力扣(LeetCode)

看见这题的时候我就觉得很像数组去重那一题,于是我沿用那一题的双指针技巧,把零全部覆盖掉,然后再把后面的数字改成0
在这里插入图片描述
代码:

class Solution {
public:
    void moveZeroes(vector<int>& nums) {
        int L = 0,N = nums.size();
        for(int R = 0;R<N;++R)
        {//覆盖0
            if(nums[R] != 0)
            {
                nums[L] = nums[R];
                L++;
            }
        }
        for(;L<N;L++)
        {//在末尾补上0
            nums[L] = 0;
        }
    }
};


来个简单的爽一爽:

633. 平方数之和

给定一个非负整数 c ,你要判断是否存在两个整数 a 和 b,使得 a^2 + b^2 = c 。
示例 :

输入:c = 5
输出:true
解释:1 * 1 + 2 * 2 = 5
来源:力扣(LeetCode)

用双指针,在0到sqrt(c)之间不断的缩小范围;

class Solution {
public:
    bool judgeSquareSum(int c) {
		int L = 0, R = (int)sqrt(c);
		while(L <= R)
		{
			long temp = (long)L*L+R*R;
			if(temp == c) return true;
			else if(temp < c) L++;
			else R--;
		}
		return false;
    }
};


424. 替换后的最长重复字符

给你一个仅由大写英文字母组成的字符串,你可以将任意位置上的字符替换成另外的字符,总共可最多替换 k 次。在执行上述操作后,找到包含重复字母的最长子串的长度。

注意:字符串长度 和 k 不会超过 104。
示例 1:
输入:s = “ABAB”, k = 2
输出:4
解释:用两个’A’替换为两个’B’,反之亦然。
来源:力扣(LeetCode)

这一题的思路其实挺简单的,看了题解,上vs跑了一下瞬间就明白了,但是我自己想的话就是想不出来。

思路:

用两个指针维护一个窗口,用一个变量maxcount维护窗口内的所有字母中出现次数最多的字母的次数,这里注意,这个次数是在窗口内出现的次数,窗口外的不算,然后判断当前窗口长度 - maxcount的值是否 <= k 只要 <= k,这个窗口就有效,继续往右扩张,寻找边界

如果 > k ,再往右扩张也没用了,我解释一下,这时再往右扩张,就算继续出现最多次数的字母,即maxcount继续增大,也没有意义,因为窗口的长度也在增大,这个窗口就一直无意义,所以这时我们得缩小左边界

为什么是左边界?因为缩小右边界无意义啊,只不过是回到以前得状态而已,所以缩小左边界

这里得注意,我们前面说了,maxcount只维护在窗口内出现的的最大次数,窗口外的不算,于是在我们缩小左边界的时候,就得把被踢出去的字母的计数-1

然后在用一个变量res维护最大的有效窗口的长度,思路总体来说就是这样

用个例子来看看:

在这里插入图片描述
可以发现,在最后一步时才收缩,收缩后A的计数减1, 因为我们把最左边的A踢了出去,此时在窗口BCAB中,A只出现了一次

最后说一点,我们用一个长度为26的数组来计数,因为我们不确定会出现多少种字母,但是最多就26种

代码:

class Solution {
public:
    int characterReplacement(string s, int k) {
        vector<int>count(26,0);
 		int maxcount = 0, left = 0, right = 0;
 		int res = 0;
 		for(;right<s.size();++right)
 		{
 			++count[s[right]-'A'];//对当前字母的计数加1
 			maxcount = max(maxcount, count[s[right]-'A'])//维护窗口中的最大出现次数
 			if(right-left+1-maxcount > k)//如果窗口无意义,就缩减左边界并让最左边的字母计数减1
 			{//可以直接写成--count[s[left++]-'A'];
 				--count[s[left]-'A'];//让最左边的字母计数减1
 				++left;//缩小左边界
 			}
 			res = max(res, right-left+1);//维护最大有效窗口长度
 		}
 		return res;
    }
};


76. 最小覆盖子串

给你一个字符串 s 、一个字符串 t 。返回 s 中涵盖 t 所有字符的最小子串。如果 s 中不存在涵盖 t 所有字符的子串,则返回空字符串 “” 。

注意:如果 s 中存在这样的子串,我们保证它是唯一的答案。

示例 1:

输入:s = “ADOBECODEBANC”, t = “ABC”
输出:“BANC”

来源:力扣(LeetCode)

这题的意思其实是要在s中找一个子串,它包含了t中的所有字符(包括重复的)

思路就是滑动窗口+哈希表,看题解很多都是直接建一个长度为128的数组,我就觉得太长了,于是用哈希表写了个
具体看注释

class Solution {
    bool check(unordered_map<char,pair<int,int>>&map,string&s, int& L)
    {//检查当前字符是否无效
        return map.find(s[L])==map.end() || (map.find(s[L])!=map.end() && map[s[L]].second>map[s[L]].first);
    }

public:
    string minWindow(string s, string t) {
        int L = 0, R = 0, count = 0, start = 0, minLen = INT_MAX;;
        unordered_map<char,pair<int,int>>map;
        for(char&ch:t)++map[ch].first;//first记录出现的次数,因为可能会有重复字符...
        while(R < s.size())
        {
            char ch = s[R];
            if(map.find(s[R])!=map.end())
            {
                if(map[s[R]].second < map[s[R]].first)//只要还是<first的,就是有效的
                {
                    ++count;//表示找到一个有效字符
                }
                ++map[s[R]].second;//计数滑动窗口内字符出现的次数
            }
            if(count == t.size())//说明当前窗口已经覆盖了t
            {
                while(L < R && check(map, s, L))//缩小无效的左边界
                {//规则是,不在t(哈希表)中  或者  在窗口中出现的次数太多,比first还大
                    if(map.find(s[L])!=map.end())
                    --map[s[L]].second;
                    ++L;
                }
                int temp = minLen;
                minLen = min(R-L+1,minLen);//长度保持最小
                if(temp != minLen)//只有找到了更小的长度时才更新start
                start = L;
                --map[s[L++]].second;//放弃当前窗口(缩小有效的左边界,时当前窗口无效),
                                    //向右寻找更多的可能性,顺便把second值也更新了
                --count;//表示当前窗口内少了一个有效字符
            }
            ++R;
        }
        if(minLen == INT_MAX)return "";//如果长度没变过,就说明根本没有能覆盖t的子串
        else
        return s.substr(start, minLen);
    }
};
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

timathy33

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值